diff --git a/src/components/common/CustomEmojiPicker.module.scss b/src/components/common/CustomEmojiPicker.module.scss index 51745e872..7c294da25 100644 --- a/src/components/common/CustomEmojiPicker.module.scss +++ b/src/components/common/CustomEmojiPicker.module.scss @@ -1,5 +1,14 @@ .root { - --emoji-size: 2.5rem; + --emoji-size: 2.25rem; + height: 100%; max-height: calc(100 * var(--vh)); + + :global(.symbol-set-container) { + --symbol-set-gap-size: 0.625rem; + } +} + +.activated { + background-color: var(--color-interactive-element-hover); } diff --git a/src/components/common/CustomEmojiPicker.tsx b/src/components/common/CustomEmojiPicker.tsx index 9be2e6fb1..c57cad4b4 100644 --- a/src/components/common/CustomEmojiPicker.tsx +++ b/src/components/common/CustomEmojiPicker.tsx @@ -1,6 +1,5 @@ -import type { RefObject } from 'react'; import React, { - useState, useEffect, memo, useRef, useMemo, useCallback, + useEffect, memo, useRef, useMemo, useCallback, } from '../../lib/teact/teact'; import { getGlobal, withGlobal } from '../../global'; @@ -21,9 +20,9 @@ import { STICKER_SIZE_PICKER_HEADER, TOP_SYMBOL_SET_ID, } from '../../config'; +import { REM } from './helpers/mediaDimensions'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; -import animateScroll from '../../util/animateScroll'; import buildClassName from '../../util/buildClassName'; import animateHorizontalScroll from '../../util/animateHorizontalScroll'; import { pickTruthy, unique } from '../../util/iteratees'; @@ -39,6 +38,7 @@ import useHorizontalScroll from '../../hooks/useHorizontalScroll'; import useLang from '../../hooks/useLang'; import useAppLayout from '../../hooks/useAppLayout'; import { useStickerPickerObservers } from './hooks/useStickerPickerObservers'; +import useScrolledState from '../../hooks/useScrolledState'; import Loading from '../ui/Loading'; import Button from '../ui/Button'; @@ -46,12 +46,10 @@ import StickerButton from './StickerButton'; import StickerSet from './StickerSet'; import StickerSetCover from '../middle/composer/StickerSetCover'; -import '../middle/composer/StickerPicker.scss'; +import pickerStyles from '../middle/composer/StickerPicker.module.scss'; import styles from './CustomEmojiPicker.module.scss'; type OwnProps = { - scrollContainerRef?: RefObject; - scrollHeaderRef?: RefObject; chatId?: string; className?: string; isHidden?: boolean; @@ -63,6 +61,7 @@ type OwnProps = { selectedReactionIds?: string[]; isStatusPicker?: boolean; isReactionPicker?: boolean; + isTranslucent?: boolean; onContextMenuOpen?: NoneToVoidFunction; onContextMenuClose?: NoneToVoidFunction; onContextMenuClick?: NoneToVoidFunction; @@ -85,8 +84,8 @@ type StateProps = { isCurrentUserPremium?: boolean; }; -const SMOOTH_SCROLL_DISTANCE = 100; -const HEADER_BUTTON_WIDTH = 52; // px (including margin) +const HEADER_BUTTON_WIDTH = 2.5 * REM; // px (including margin) + const DEFAULT_ID_PREFIX = 'custom-emoji-set'; const TOP_REACTIONS_COUNT = 16; const RECENT_REACTIONS_COUNT = 32; @@ -100,8 +99,6 @@ const STICKER_SET_IDS_WITH_COVER = new Set([ ]); const CustomEmojiPicker: FC = ({ - scrollContainerRef, - scrollHeaderRef, className, isHidden, loadAndPlay, @@ -119,6 +116,7 @@ const CustomEmojiPicker: FC = ({ canAnimate, isReactionPicker, isStatusPicker, + isTranslucent, isSavedMessages, isCurrentUserPremium, withDefaultTopicIcons, @@ -131,22 +129,19 @@ const CustomEmojiPicker: FC = ({ onContextMenuClick, }) => { // eslint-disable-next-line no-null/no-null - let containerRef = useRef(null); + const containerRef = useRef(null); // eslint-disable-next-line no-null/no-null - let headerRef = useRef(null); + const headerRef = useRef(null); // eslint-disable-next-line no-null/no-null const sharedCanvasRef = useRef(null); // eslint-disable-next-line no-null/no-null const sharedCanvasHqRef = useRef(null); - if (scrollContainerRef) { - containerRef = scrollContainerRef; - } - if (scrollHeaderRef) { - headerRef = scrollHeaderRef; - } - const [activeSetIndex, setActiveSetIndex] = useState(0); const { isMobile } = useAppLayout(); + const { + handleScroll: handleContentScroll, + isAtBeginning: shouldHideTopBorder, + } = useScrolledState(); const recentCustomEmojis = useMemo(() => { return isStatusPicker @@ -155,11 +150,13 @@ const CustomEmojiPicker: FC = ({ }, [customEmojisById, isStatusPicker, recentCustomEmojiIds, recentStatusEmojis]); const { + activeSetIndex, observeIntersectionForSet, observeIntersectionForPlayingItems, observeIntersectionForShowingItems, observeIntersectionForCovers, - } = useStickerPickerObservers(containerRef, headerRef, idPrefix, setActiveSetIndex, isHidden); + selectStickerSet, + } = useStickerPickerObservers(containerRef, headerRef, idPrefix, isHidden); const lang = useLang(); @@ -260,7 +257,7 @@ const CustomEmojiPicker: FC = ({ const canRenderContent = useAsyncRendering([], SLIDE_TRANSITION_DURATION); const shouldRenderContent = areAddedLoaded && canRenderContent && !noPopulatedSets; - useHorizontalScroll(headerRef, !(isMobile && shouldRenderContent)); + useHorizontalScroll(headerRef, isMobile || !shouldRenderContent); // Scroll container and header when active set changes useEffect(() => { @@ -278,12 +275,6 @@ const CustomEmojiPicker: FC = ({ animateHorizontalScroll(header, newLeft); }, [areAddedLoaded, activeSetIndex]); - const selectStickerSet = useCallback((index: number) => { - setActiveSetIndex(index); - const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!; - animateScroll(containerRef.current!, stickerSetEl, 'start', undefined, SMOOTH_SCROLL_DISTANCE); - }, [idPrefix]); - const handleEmojiSelect = useCallback((emoji: ApiSticker) => { onCustomEmojiSelect(emoji); }, [onCustomEmojiSelect]); @@ -295,8 +286,8 @@ const CustomEmojiPicker: FC = ({ function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) { const firstSticker = stickerSet.stickers?.[0]; const buttonClassName = buildClassName( - 'symbol-set-button sticker-set-button', - index === activeSetIndex && 'activated', + pickerStyles.stickerCover, + index === activeSetIndex && styles.activated, ); const withSharedCanvas = index < STICKER_PICKER_MAX_SHARED_COVERS; @@ -307,6 +298,7 @@ const CustomEmojiPicker: FC = ({ } if (STICKER_SET_IDS_WITH_COVER.has(stickerSet.id) || stickerSet.hasThumbnail || !firstSticker) { + const isRecent = stickerSet.id === RECENT_SYMBOL_SET_ID || stickerSet.id === POPULAR_SYMBOL_SET_ID; const isFaded = FADED_BUTTON_SET_IDS.has(stickerSet.id); return ( @@ -195,8 +202,8 @@ const StickerSetModal: FC = ({ > {renderingStickerSet?.stickers ? ( <> -
-
+
+
{renderingStickerSet.stickers.map((sticker) => ( = ({ const thumbDataUri = useThumbnail(sticker); // Use preview instead of thumb but only if it's already loaded const [preloadedPreviewData] = useState(mediaLoader.getFromMemory(previewMediaHash)); - const thumbData = preloadedPreviewData || thumbDataUri; + const thumbData = customColor ? thumbDataUri : (preloadedPreviewData || thumbDataUri); const shouldForcePreview = isUnsupportedVideo || (isStatic && isSmall); fullMediaHash ||= shouldForcePreview ? previewMediaHash : `sticker${id}`; @@ -138,6 +138,7 @@ const StickerView: FC = ({ isThumbOpaque && styles.thumbOpaque, thumbClassName, thumbClassNames, + 'sticker-media', )} alt="" draggable={false} @@ -168,7 +169,7 @@ const StickerView: FC = ({ ) : isVideo ? ( = ({ /> ) : ( {emoji}, headerRef: RefObject, idPrefix: string, - setActiveSetIndex: (index: number) => void, isHidden?: boolean, ) { const stickerSetIntersectionsRef = useRef([]); + const [activeSetIndex, setActiveSetIndex] = useState(0); + const { observe: observeIntersectionForSet, freeze: freezeForSet, @@ -79,10 +85,29 @@ export function useStickerPickerObservers( } }, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]); + const selectStickerSet = useCallback((index: number) => { + setActiveSetIndex((currentIndex) => { + const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!; + const isClose = Math.abs(currentIndex - index) === 1; + + animateScroll( + containerRef.current!, + stickerSetEl, + 'start', + FOCUS_MARGIN, + isClose ? SCROLL_MAX_DISTANCE_WHEN_CLOSE : SCROLL_MAX_DISTANCE_WHEN_FAR, + ); + + return index; + }); + }, [containerRef, idPrefix]); + return { + activeSetIndex, observeIntersectionForSet, observeIntersectionForShowingItems, observeIntersectionForPlayingItems, observeIntersectionForCovers, + selectStickerSet, }; } diff --git a/src/components/left/main/StatusPickerMenu.module.scss b/src/components/left/main/StatusPickerMenu.module.scss index fe06352a4..784c8ac05 100644 --- a/src/components/left/main/StatusPickerMenu.module.scss +++ b/src/components/left/main/StatusPickerMenu.module.scss @@ -2,12 +2,19 @@ --offset-y: 3.25rem !important; --offset-x: auto !important; --color-text: var(--color-primary); + --color-background: var(--color-background-compact-menu); + --border-radius-default: 1.25rem; left: 0.5rem; width: 100%; - max-width: 26rem; - height: 26rem; + max-width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar + height: var(--symbol-menu-height); padding: 0 !important; + backdrop-filter: blur(10px); + + @supports (overflow: overlay) { + width: var(--symbol-menu-width); + } @media (max-width: 26rem) { left: 0.5rem !important; diff --git a/src/components/left/main/StatusPickerMenu.tsx b/src/components/left/main/StatusPickerMenu.tsx index 980cb2f5b..a84570789 100644 --- a/src/components/left/main/StatusPickerMenu.tsx +++ b/src/components/left/main/StatusPickerMenu.tsx @@ -35,11 +35,6 @@ const StatusPickerMenu: FC = ({ }) => { const { loadFeaturedEmojiStickers } = getActions(); - // eslint-disable-next-line no-null/no-null - const scrollHeaderRef = useRef(null); - // eslint-disable-next-line no-null/no-null - const scrollContainerRef = useRef(null); - const transformOriginX = useRef(); const [isContextMenuShown, markContextMenuShown, unmarkContextMenuShown] = useFlag(); useEffect(() => { @@ -52,15 +47,6 @@ const StatusPickerMenu: FC = ({ } }, [areFeaturedStickersLoaded, isOpen, loadFeaturedEmojiStickers]); - const handleResetScrollPosition = useCallback(() => { - if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTop = 0; - } - if (scrollHeaderRef.current) { - scrollHeaderRef.current.scrollLeft = 0; - } - }, []); - const handleEmojiSelect = useCallback((sticker: ApiSticker) => { onEmojiStatusSelect(sticker); onClose(); @@ -76,14 +62,13 @@ const StatusPickerMenu: FC = ({ onClose={onClose} transformOriginX={transformOriginX.current} noCloseOnBackdrop={isContextMenuShown} - onCloseAnimationEnd={handleResetScrollPosition} > = ({ const { isMobile } = useAppLayout(); const emojisPerRow = isMobile - ? Math.floor((windowSize.get().width - MOBILE_CONTAINER_PADDING) / (EMOJI_SIZE + EMOJI_MARGIN)) + ? Math.floor( + (windowSize.get().width - MOBILE_CONTAINER_PADDING + EMOJI_MARGIN) / (EMOJI_SIZE_PICKER + EMOJI_MARGIN), + ) : EMOJIS_PER_ROW_ON_DESKTOP; - const height = Math.ceil(category.emojis.length / emojisPerRow) * (EMOJI_SIZE + EMOJI_MARGIN); + const height = Math.ceil(category.emojis.length / emojisPerRow) + * (EMOJI_SIZE_PICKER + (isMobile ? EMOJI_VERTICAL_MARGIN_MOBILE : EMOJI_VERTICAL_MARGIN)); return (
= { const OPEN_ANIMATION_DELAY = 200; const SMOOTH_SCROLL_DISTANCE = 100; -const FOCUS_MARGIN = 50; -const HEADER_BUTTON_WIDTH = 42; // px. Includes margins +const FOCUS_MARGIN = 3.25 * REM; +const HEADER_BUTTON_WIDTH = 2.625 * REM; // Includes margins const INTERSECTION_THROTTLE = 200; const categoryIntersections: boolean[] = []; @@ -78,6 +80,10 @@ const EmojiPicker: FC = ({ const [emojis, setEmojis] = useState(); const [activeCategoryIndex, setActiveCategoryIndex] = useState(0); const { isMobile } = useAppLayout(); + const { + handleScroll: handleContentScroll, + isAtBeginning: shouldHideTopBorder, + } = useScrolledState(); const { observe: observeIntersection } = useIntersectionObserver({ rootRef: containerRef, @@ -200,13 +206,23 @@ const EmojiPicker: FC = ({ ); } + const headerClassName = buildClassName( + 'EmojiPicker-header', + !shouldHideTopBorder && 'with-top-border', + ); + return (
-
+
{allCategories.map(renderCategoryButton)}
{allCategories.map((category, i) => ( diff --git a/src/components/middle/composer/EmojiTooltip.scss b/src/components/middle/composer/EmojiTooltip.scss index aae0d789a..e1c2450bf 100644 --- a/src/components/middle/composer/EmojiTooltip.scss +++ b/src/components/middle/composer/EmojiTooltip.scss @@ -9,7 +9,7 @@ overflow-y: hidden; .EmojiButton { - flex: 0 0 2.5rem; + flex: 0 0 2.25rem; margin-right: 0; } } diff --git a/src/components/middle/composer/GifPicker.scss b/src/components/middle/composer/GifPicker.scss index c928d7e04..af3a14fb6 100644 --- a/src/components/middle/composer/GifPicker.scss +++ b/src/components/middle/composer/GifPicker.scss @@ -1,19 +1,31 @@ +@import "../../../styles/mixins"; + .GifPicker { display: grid; grid-template-columns: repeat(6, 1fr); grid-auto-rows: 6.25rem; - grid-gap: 0.25rem; + grid-gap: 0.125rem; grid-auto-flow: dense; - height: 100%; + height: calc(100% - 0.1875rem); overflow-y: auto; - padding: 0.25rem; + margin: 0 0.1875rem; + padding-bottom: 0.1875rem; + border-radius: 1.125rem 1.125rem 0 0; + position: relative; + top: 0.1875rem; - @supports (overflow: overlay) { - overflow-y: overlay; - } + @include overflow-y-overlay(); .Loading, .picker-disabled { grid-column: 1 / -1; height: var(--menu-height); } + + .SymbolMenu.mobile-menu & { + border-radius: 0; + } + + .bubble { + border-radius: var(--border-radius-default) !important; + } } diff --git a/src/components/middle/composer/GifPicker.tsx b/src/components/middle/composer/GifPicker.tsx index 66768fd64..b6d6bbef1 100644 --- a/src/components/middle/composer/GifPicker.tsx +++ b/src/components/middle/composer/GifPicker.tsx @@ -63,29 +63,31 @@ const GifPicker: FC = ({ const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION); return ( -
- {!canSendGifs ? ( -
Sending GIFs is not allowed in this chat.
- ) : canRenderContents && savedGifs && savedGifs.length ? ( - savedGifs.map((gif) => ( - - )) - ) : canRenderContents && savedGifs ? ( -
No saved GIFs.
- ) : ( - - )} +
+
+ {!canSendGifs ? ( +
Sending GIFs is not allowed in this chat.
+ ) : canRenderContents && savedGifs && savedGifs.length ? ( + savedGifs.map((gif) => ( + + )) + ) : canRenderContents && savedGifs ? ( +
No saved GIFs.
+ ) : ( + + )} +
); }; diff --git a/src/components/middle/composer/StickerPicker.module.scss b/src/components/middle/composer/StickerPicker.module.scss new file mode 100644 index 000000000..e3204a3b7 --- /dev/null +++ b/src/components/middle/composer/StickerPicker.module.scss @@ -0,0 +1,112 @@ +@import "../../../styles/mixins"; + +.root { + --color-primary: var(--color-text); + + height: 100%; +} + +.main { + --symbol-set-gap-size: 0.25rem; + + position: relative; + height: calc(100% - 3rem); + overflow-y: auto; + padding: 0.5rem 0.25rem; + + @include overflow-y-overlay(); + + &_customEmoji { + padding: 0.5rem 0.75rem; + } + + :global(.bubble) { + border-radius: var(--border-radius-default) !important; + } +} + +.header { + height: 3rem; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + padding: 0 0.375rem; + + scrollbar-width: none; + scrollbar-color: rgba(0, 0, 0, 0); + + &::-webkit-scrollbar { + height: 0; + } + + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0); + } + + // Spacer to counter last button margin not being accounted in scroll width + &::after { + content: ""; + display: block; + flex-shrink: 0; + width: 1px; + height: 1rem; + } + + &.headerWithBorder { + /* stylelint-disable-next-line plugin/whole-pixel */ + box-shadow: inset 0 -0.5px 0 0 var(--color-borders-alternate); + } + + // Allows `shared-canvas` to fill the whole available width + & > :global(.shared-canvas-container) { + display: inline-block; + } + + :global(.shared-canvas) { + max-width: 1280px; // STICKER_PICKER_MAX_SHARED_COVERS * (STICKER_SIZE_PICKER_HEADER + 10 * 2) + z-index: 1; + } +} + +.stickerCover { + display: inline-grid; + vertical-align: middle; + grid-template-areas: "cover"; + justify-content: center; + align-items: center; + + width: 2.25rem !important; + height: 2.25rem; + margin: 0.375rem 0.125rem !important; + padding: 0; + border-radius: var(--border-radius-messages-small) !important; + + &:global(.StickerButton) { + background-size: 1.875rem; + + :global(.sticker-media), + :global(.AnimatedSticker) { + position: static; + grid-area: cover; + width: 1.875rem; + height: 1.875rem; + } + } + + &.activated { + color: var(--color-text-lighter); + background-color: var(--color-interactive-element-hover); + + :global(.theme-dark) & { + --color-text-lighter: var(--color-text); + } + } +} + +.pickerDisabled { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/components/middle/composer/StickerPicker.scss b/src/components/middle/composer/StickerPicker.scss deleted file mode 100644 index 7edfbea00..000000000 --- a/src/components/middle/composer/StickerPicker.scss +++ /dev/null @@ -1,109 +0,0 @@ -.StickerPicker { - height: 100%; - - &-main { - position: relative; - height: calc(100% - 3rem); - overflow-y: auto; - padding: 0.5rem 0.25rem; - } - - &-header { - height: 3rem; - border-bottom: 1px solid var(--color-borders); - padding: 0.125rem 0; - overflow-x: auto; - overflow-y: hidden; - white-space: nowrap; - box-shadow: 0 0 2px var(--color-default-shadow); - - scrollbar-width: none; - scrollbar-color: rgba(0, 0, 0, 0); - - &::-webkit-scrollbar { - height: 0; - } - - &::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0); - } - - // Spacer to counter last button margin not being accounted in scroll width - &::after { - content: ""; - display: block; - flex-shrink: 0; - width: 1px; - height: 1rem; - } - - - .sticker-set-button { - display: inline-flex; - vertical-align: middle; - align-items: center; - justify-content: center; - - &.StickerButton { - background-size: 2rem; - - video, img, .AnimatedSticker { - top: 0.375rem; - left: 0.375rem; - width: 2rem; - height: 2rem; - } - } - - .sticker-set-cover { - width: 2rem; - height: 2rem; - position: relative; - display: flex; - align-items: center; - justify-content: center; - - video, img, .AnimatedSticker { - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - } - } - - &.activated { - background-color: var(--color-background-selected); - } - } - - .shared-canvas-container { - display: inline-block; - } - - .shared-canvas { - max-width: 1280px; // STICKER_PICKER_MAX_SHARED_COVERS * (STICKER_SIZE_PICKER_HEADER + 10 * 2) - z-index: 1; - } - } - - .symbol-set-container { - width: 100%; - line-height: 0; - } - - .sticker-set-button { - width: 2.75rem !important; - height: 2.75rem; - margin: 0 0.25rem; - border-radius: var(--border-radius-messages-small); - } - - .picker-disabled { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - } -} diff --git a/src/components/middle/composer/StickerPicker.tsx b/src/components/middle/composer/StickerPicker.tsx index e5e1135e9..69a16e141 100644 --- a/src/components/middle/composer/StickerPicker.tsx +++ b/src/components/middle/composer/StickerPicker.tsx @@ -1,5 +1,5 @@ import React, { - useState, useEffect, memo, useRef, useMemo, useCallback, + useEffect, memo, useRef, useMemo, useCallback, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -16,10 +16,10 @@ import { STICKER_PICKER_MAX_SHARED_COVERS, STICKER_SIZE_PICKER_HEADER, } from '../../../config'; +import { REM } from '../../common/helpers/mediaDimensions'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { isUserId } from '../../../global/helpers'; -import animateScroll from '../../../util/animateScroll'; import buildClassName from '../../../util/buildClassName'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; import { pickTruthy, uniqueByField } from '../../../util/iteratees'; @@ -32,6 +32,7 @@ import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useLang from '../../../hooks/useLang'; import useSendMessageAction from '../../../hooks/useSendMessageAction'; import { useStickerPickerObservers } from '../../common/hooks/useStickerPickerObservers'; +import useScrolledState from '../../../hooks/useScrolledState'; import Avatar from '../../common/Avatar'; import Loading from '../../ui/Loading'; @@ -41,13 +42,14 @@ import StickerSet from '../../common/StickerSet'; import StickerSetCover from './StickerSetCover'; import PremiumIcon from '../../common/PremiumIcon'; -import './StickerPicker.scss'; +import styles from './StickerPicker.module.scss'; type OwnProps = { chatId: string; threadId?: number; className: string; isHidden?: boolean; + isTranslucent?: boolean; loadAndPlay: boolean; canSendStickers?: boolean; onStickerSelect: ( @@ -68,14 +70,14 @@ type StateProps = { isCurrentUserPremium?: boolean; }; -const SMOOTH_SCROLL_DISTANCE = 100; -const HEADER_BUTTON_WIDTH = 52; // px (including margin) +const HEADER_BUTTON_WIDTH = 2.5 * REM; // px (including margin) const StickerPicker: FC = ({ chat, threadId, className, isHidden, + isTranslucent, loadAndPlay, canSendStickers, recentStickers, @@ -104,16 +106,21 @@ const StickerPicker: FC = ({ // eslint-disable-next-line no-null/no-null const sharedCanvasRef = useRef(null); - const [activeSetIndex, setActiveSetIndex] = useState(0); + const { + handleScroll: handleContentScroll, + isAtBeginning: shouldHideTopBorder, + } = useScrolledState(); const sendMessageAction = useSendMessageAction(chat!.id, threadId); const { + activeSetIndex, observeIntersectionForSet, observeIntersectionForPlayingItems, observeIntersectionForShowingItems, observeIntersectionForCovers, - } = useStickerPickerObservers(containerRef, headerRef, 'sticker-set', setActiveSetIndex, isHidden); + selectStickerSet, + } = useStickerPickerObservers(containerRef, headerRef, 'sticker-set', isHidden); const lang = useLang(); @@ -204,7 +211,7 @@ const StickerPicker: FC = ({ const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION); const shouldRenderContents = areAddedLoaded && canRenderContents && !noPopulatedSets && canSendStickers; - useHorizontalScroll(headerRef, !shouldRenderContents); + useHorizontalScroll(headerRef, !shouldRenderContents || !headerRef.current); // Scroll container and header when active set changes useEffect(() => { @@ -222,12 +229,6 @@ const StickerPicker: FC = ({ animateHorizontalScroll(header, newLeft); }, [areAddedLoaded, activeSetIndex]); - const selectStickerSet = useCallback((index: number) => { - setActiveSetIndex(index); - const stickerSetEl = document.getElementById(`sticker-set-${index}`)!; - animateScroll(containerRef.current!, stickerSetEl, 'start', undefined, SMOOTH_SCROLL_DISTANCE); - }, []); - const handleStickerSelect = useCallback((sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => { onStickerSelect(sticker, isSilent, shouldSchedule, true); addRecentSticker({ sticker }); @@ -252,11 +253,7 @@ const StickerPicker: FC = ({ function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) { const firstSticker = stickerSet.stickers?.[0]; - const buttonClassName = buildClassName( - 'symbol-set-button sticker-set-button', - index === activeSetIndex && 'activated', - ); - + const buttonClassName = buildClassName(styles.stickerCover, index === activeSetIndex && styles.activated); const withSharedCanvas = index < STICKER_PICKER_MAX_SHARED_COVERS; if (stickerSet.id === RECENT_SYMBOL_SET_ID @@ -308,6 +305,7 @@ const StickerPicker: FC = ({ noContextMenu isCurrentUserPremium sharedCanvasRef={withSharedCanvas ? sharedCanvasRef : undefined} + withTranslucentThumb={isTranslucent} onClick={selectStickerSet} clickArg={index} /> @@ -315,15 +313,15 @@ const StickerPicker: FC = ({ } } - const fullClassName = buildClassName('StickerPicker', className); + const fullClassName = buildClassName(styles.root, className); if (!shouldRenderContents) { return (
{!canSendStickers ? ( -
{lang('ErrorSendRestrictedStickersAll')}
+
{lang('ErrorSendRestrictedStickersAll')}
) : noPopulatedSets ? ( -
{lang('NoStickers')}
+
{lang('NoStickers')}
) : ( )} @@ -331,12 +329,15 @@ const StickerPicker: FC = ({ ); } + const headerClassName = buildClassName( + styles.header, + 'no-selection no-scrollbar', + !shouldHideTopBorder && styles.headerWithBorder, + ); + return (
-
+
{allSets.map(renderCover)} @@ -345,7 +346,8 @@ const StickerPicker: FC = ({
{allSets.map((stickerSet, i) => ( = ({ favoriteStickers={favoriteStickers} isSavedMessages={isSavedMessages} isCurrentUserPremium={isCurrentUserPremium} + isTranslucent={isTranslucent} onStickerSelect={handleStickerSelect} onStickerUnfave={handleStickerUnfave} onStickerFave={handleStickerFave} diff --git a/src/components/middle/composer/StickerSetCover.module.scss b/src/components/middle/composer/StickerSetCover.module.scss index 11e7b0e1c..ec8990b33 100644 --- a/src/components/middle/composer/StickerSetCover.module.scss +++ b/src/components/middle/composer/StickerSetCover.module.scss @@ -1,3 +1,19 @@ -.video { - width: 100%; +.root { + width: 1.875rem; + height: 1.875rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--border-radius-messages-small) !important; + + :global(.AnimatedSticker) { + width: 100%; + height: 100%; + } +} + +.video, +.image { + width: 100%; + height: 100%; } diff --git a/src/components/middle/composer/StickerSetCover.tsx b/src/components/middle/composer/StickerSetCover.tsx index bc298c97c..ebe200528 100644 --- a/src/components/middle/composer/StickerSetCover.tsx +++ b/src/components/middle/composer/StickerSetCover.tsx @@ -68,7 +68,7 @@ const StickerSetCover: FC = ({ }, [isIntersecting, loadStickers, stickerSet]); return ( -
+
{isReady ? ( isLottie ? ( = ({ ) : ( ) diff --git a/src/components/middle/composer/SymbolMenu.scss b/src/components/middle/composer/SymbolMenu.scss index 51f372206..2534c2691 100644 --- a/src/components/middle/composer/SymbolMenu.scss +++ b/src/components/middle/composer/SymbolMenu.scss @@ -3,13 +3,7 @@ .SymbolMenu { &.attachment-modal-symbol-menu { position: absolute; - z-index: 10000; - } - - &:not(.mobile-menu) { - @media (max-height: 800px) { - --symbol-menu-height: 40vh; - } + z-index: var(--z-symbol-menu-modal); } &.mobile-menu { @@ -24,19 +18,19 @@ padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); - transform: translate3d(0, calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height) + env(safe-area-inset-bottom)), 0); + transform: translate3d(0, calc(var(--symbol-menu-height) + env(safe-area-inset-bottom)), 0); &.open:not(.in-attachment-modal) { transform: translate3d(0, 0, 0); body.is-media-viewer-open & { - transform: translate3d(0, calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height)), 0); + transform: translate3d(0, calc(var(--symbol-menu-height)), 0); } } &.open.in-attachment-modal { z-index: calc(var(--z-modal) + 1); - transform: translate3d(0, calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height)), 0); + transform: translate3d(0, calc(var(--symbol-menu-height)), 0); } // Target: Old Firefox (Waterfox Classic) @@ -44,7 +38,7 @@ padding-right: 0; padding-bottom: 0; padding-left: 0; - transform: translate3d(0, calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height)), 0); + transform: translate3d(0, calc(var(--symbol-menu-height)), 0); } body.animation-level-0 & { @@ -57,7 +51,7 @@ } &-main { - height: var(--symbol-menu-height); + height: calc(var(--symbol-menu-height) - var(--symbol-menu-footer-height)); max-height: calc(100vh - var(--symbol-menu-footer-height) - env(safe-area-inset-bottom)); // Target: Old Firefox (Waterfox Classic) @@ -68,11 +62,11 @@ &-footer { height: var(--symbol-menu-footer-height); - border-top: 1px solid var(--color-borders); display: flex; align-items: center; justify-content: center; - box-shadow: 0 0 2px var(--color-default-shadow); + /* stylelint-disable-next-line plugin/whole-pixel */ + box-shadow: 0 -0.5px var(--color-borders-alternate); position: relative; .Button { @@ -84,6 +78,7 @@ &.activated { pointer-events: none; + color: var(--color-text); } &.symbol-tab-button { @@ -145,6 +140,9 @@ .bubble { --offset-y: 4rem; + background: var(--color-background-compact-menu); + backdrop-filter: blur(10px); + border-radius: 1.25rem; width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar padding: 0; overflow: hidden; @@ -160,6 +158,10 @@ } } + .StickerButton.custom-emoji { + color: var(--color-text); + } + .picker-disabled { height: var(--symbol-menu-height); display: flex; @@ -189,20 +191,11 @@ } .symbol-set { - margin-bottom: 1rem; + margin-bottom: 0.75rem; position: relative; display: flex; flex-direction: column; - &.symbol-set-locked::before { - content: ""; - display: block; - position: absolute; - inset: -0.25rem; - top: 0.75rem; - background: url("data:image/svg+xml;utf8,"); - } - &-header { display: flex; align-items: center; @@ -215,14 +208,33 @@ line-height: 1.6875rem; font-weight: 500; margin: 0; - padding: 0 0.5rem; + padding: 0 0.25rem 0.125rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-align: center; unicode-bidi: plaintext; z-index: 1; - background-color: var(--color-background); + + &-external { + color: var(--color-text); + } + } + + &-amount { + display: block; + font-size: 0.875rem; + font-weight: normal; + text-align: left; + line-height: 1.125rem; + margin-top: -0.125rem; + margin-bottom: 0.125rem; + color: rgba(var(--color-text-secondary-rgb), 0.75); + unicode-bidi: plaintext; + + .symbol-set-locked & { + padding-left: 1.25rem; + } } &-locked-icon { @@ -266,9 +278,9 @@ .symbol-set-container { display: grid !important; justify-content: space-between; - grid-template-columns: repeat(auto-fill, var(--emoji-size, 4rem)); - grid-gap: 0.625rem; - padding: 0.3125rem; + grid-template-columns: repeat(auto-fill, var(--emoji-size, 4.5rem)); + row-gap: 0.25rem; + column-gap: var(--symbol-set-gap-size, 0.625rem); text-align: initial; @media (max-width: 600px) { diff --git a/src/components/middle/composer/SymbolMenu.tsx b/src/components/middle/composer/SymbolMenu.tsx index dc3b8bc75..bebb33559 100644 --- a/src/components/middle/composer/SymbolMenu.tsx +++ b/src/components/middle/composer/SymbolMenu.tsx @@ -218,8 +218,9 @@ const SymbolMenu: FC = ({ className="picker-tab" isHidden={!isOpen || !isActive} loadAndPlay={isOpen && (isActive || isFrom)} - onCustomEmojiSelect={handleCustomEmojiSelect} chatId={chatId} + isTranslucent={!isMobile} + onCustomEmojiSelect={handleCustomEmojiSelect} /> ); case SymbolMenuTabs.Stickers: @@ -229,9 +230,10 @@ const SymbolMenu: FC = ({ isHidden={!isOpen || !isActive} loadAndPlay={canSendStickers ? isOpen && (isActive || isFrom) : false} canSendStickers={canSendStickers} - onStickerSelect={handleStickerSelect} chatId={chatId} threadId={threadId} + isTranslucent={!isMobile} + onStickerSelect={handleStickerSelect} /> ); case SymbolMenuTabs.GIFs: @@ -256,7 +258,11 @@ const SymbolMenu: FC = ({ <>
{isActivated && ( - + {renderContent} )} diff --git a/src/components/middle/composer/hooks/useInputCustomEmojis.ts b/src/components/middle/composer/hooks/useInputCustomEmojis.ts index d74a1a3b9..e8c35d278 100644 --- a/src/components/middle/composer/hooks/useInputCustomEmojis.ts +++ b/src/components/middle/composer/hooks/useInputCustomEmojis.ts @@ -157,7 +157,7 @@ export default function useInputCustomEmojis( }, []); const unfreezeAnimation = useCallback(() => { - playersById.current.forEach((player) => { + playersById.current?.forEach((player) => { player.play(); }); }, []); diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index 557a0941e..aea063648 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -108,8 +108,6 @@ type StateProps = { threadId?: number; }; -const REACTION_PICKER_APPEARANCE_DURATION_MS = 250; - const ContextMenuContainer: FC = ({ availableReactions, topReactions, @@ -193,7 +191,6 @@ const ContextMenuContainer: FC = ({ const lang = useLang(); const { transitionClassNames } = useShowTransition(isOpen, onCloseAnimationEnd, undefined, false); const [isMenuOpen, setIsMenuOpen] = useState(true); - const [noAnimationOnClose, setNoAnimationOnClose] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isReportModalOpen, setIsReportModalOpen] = useState(false); const [isPinModalOpen, setIsPinModalOpen] = useState(false); @@ -262,8 +259,7 @@ const ContextMenuContainer: FC = ({ setIsReportModalOpen(true); }, []); - const closeMenu = useCallback((noCloseAnimation = false) => { - setNoAnimationOnClose(noCloseAnimation); + const closeMenu = useCallback(() => { setIsMenuOpen(false); onClose(); }, [onClose]); @@ -424,10 +420,7 @@ const ContextMenuContainer: FC = ({ const handleReactionPickerOpen = useCallback((position: IAnchorPosition) => { openReactionPicker({ chatId: message.chatId, messageId: message.id, position }); - setTimeout(() => { - closeMenu(true); - }, REACTION_PICKER_APPEARANCE_DURATION_MS); - }, [closeMenu, message.chatId, message.id]); + }, [message.chatId, message.id]); const handleTranslate = useCallback(() => { requestMessageTranslation({ @@ -504,7 +497,6 @@ const ContextMenuContainer: FC = ({ canSelectLanguage={canSelectLanguage} hasCustomEmoji={hasCustomEmoji} customEmojiSets={customEmojiSets} - noTransition={noAnimationOnClose} isDownloading={isDownloading} seenByRecentUsers={seenByRecentUsers} noReplies={noReplies} diff --git a/src/components/middle/message/MessageContextMenu.scss b/src/components/middle/message/MessageContextMenu.scss index abe358b74..6dbb08372 100644 --- a/src/components/middle/message/MessageContextMenu.scss +++ b/src/components/middle/message/MessageContextMenu.scss @@ -2,14 +2,19 @@ position: absolute; font-size: 1rem; - .scrollable-content { + &_items { overflow: auto; overflow: overlay; padding: 0.5rem 0; overscroll-behavior: contain; + + &-hidden { + opacity: 0; + transition: 300ms opacity; + } } - &.compact .scrollable-content { + &.compact &_items { padding: 0.25rem 0; } @@ -25,7 +30,7 @@ padding: 3.5rem 0 0 !important; } - &.with-reactions .scrollable-content { + &.with-reactions &_items { background: var(--color-background-compact-menu); backdrop-filter: blur(10px); box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow); diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index 9c5b3d4e2..473cbc7be 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -203,8 +203,9 @@ const MessageContextMenu: FC = ({ const isSponsoredMessage = !('id' in message); const messageId = !isSponsoredMessage ? message.id : ''; + const [areItemsHidden, hideItems] = useFlag(); const [isReady, markIsReady, unmarkIsReady] = useFlag(); - const { isMobile } = useAppLayout(); + const { isMobile, isDesktop } = useAppLayout(); const handleAfterCopy = useCallback(() => { showNotification({ @@ -256,7 +257,7 @@ const MessageContextMenu: FC = ({ && (document.querySelector('.AudioPlayer-content'))?.offsetHeight) || 0; const pinnedElement = document.querySelector('.HeaderPinnedMessageWrapper'); const extraHeightPinned = (((isMobile && !extraHeightAudioPlayer) - || (!isMobile && pinnedElement?.classList.contains('full-width'))) + || (!isMobile && pinnedElement?.classList.contains('full-width'))) && pinnedElement?.offsetHeight) || 0; return { @@ -264,10 +265,10 @@ const MessageContextMenu: FC = ({ extraTopPadding: (document.querySelector('.MiddleHeader')!).offsetHeight, marginSides: withReactions ? REACTION_BUBBLE_EXTRA_WIDTH : undefined, extraMarginTop: extraHeightPinned + extraHeightAudioPlayer, - shouldAvoidNegativePosition: true, + shouldAvoidNegativePosition: !isDesktop, menuElMinWidth: withReactions && isMobile ? REACTION_SELECTOR_WIDTH_REM * REM : undefined, }; - }, [isMobile, withReactions]); + }, [isDesktop, isMobile, withReactions]); useEffect(() => { if (!isOpen) { @@ -285,11 +286,16 @@ const MessageContextMenu: FC = ({ } = useMenuPosition(anchor, getTriggerElement, getRootElement, getMenuElement, getLayout); useEffect(() => { - disableScrolling(withScroll ? scrollableRef.current : undefined, '.ReactionSelector'); + disableScrolling(withScroll ? scrollableRef.current : undefined, '.ReactionPicker'); return enableScrolling; }, [withScroll]); + const handleOpenReactionPicker = useCallback((position: IAnchorPosition) => { + onReactionPickerOpen!(position); + hideItems(); + }, [onReactionPickerOpen]); + return ( = ({ isReady={isReady} canBuyPremium={canBuyPremium} isCurrentUserPremium={isCurrentUserPremium} - onShowMore={onReactionPickerOpen!} + onShowMore={handleOpenReactionPicker} /> )}
diff --git a/src/components/middle/message/ReactionAnimatedEmoji.module.scss b/src/components/middle/message/ReactionAnimatedEmoji.module.scss index 3282b14bb..b7a1094c2 100644 --- a/src/components/middle/message/ReactionAnimatedEmoji.module.scss +++ b/src/components/middle/message/ReactionAnimatedEmoji.module.scss @@ -24,8 +24,8 @@ pointer-events: none; &.effect { - top: -2.5rem; - left: -2.5rem; + top: -2.25rem; + left: -2.25rem; } &:not(:global(.open)) { diff --git a/src/components/middle/message/ReactionPicker.module.scss b/src/components/middle/message/ReactionPicker.module.scss index e02edb8a3..9f3c551b8 100644 --- a/src/components/middle/message/ReactionPicker.module.scss +++ b/src/components/middle/message/ReactionPicker.module.scss @@ -10,14 +10,24 @@ } .menuContent { - width: 26.25rem; - height: 26.25rem; + --color-background: var(--color-background-compact-menu); + --border-radius-default: 1.25rem; + + width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar + height: var(--symbol-menu-height); padding: 0 !important; - transform-origin: 50% 3.5rem !important; + transform-origin: 9rem 4.625rem !important; + backdrop-filter: blur(10px); + + @supports (overflow: overlay) { + width: var(--symbol-menu-width); + } &:global(.bubble) { - transform: scale(0.7) !important; - transition: opacity 140ms cubic-bezier(0.2, 0, 0.2, 1), transform 140ms cubic-bezier(0.2, 0, 0.2, 1) !important; + transform: scale(0.8) !important; + transition: opacity 150ms cubic-bezier(0.2, 0, 0.2, 1), transform 150ms cubic-bezier(0.2, 0, 0.2, 1) !important; + --offset-x: -0.75rem; + --offset-y: calc(100% + 0.625rem); } &:global(.bubble.open) { @@ -42,6 +52,11 @@ .onlyReactions { height: auto; + transform-origin: 9rem 1.75rem !important; + + &:global(.bubble) { + --offset-y: calc(100% + 0.1875rem); + } } .hidden { diff --git a/src/components/middle/message/ReactionPicker.tsx b/src/components/middle/message/ReactionPicker.tsx index e666e854a..01b58de86 100644 --- a/src/components/middle/message/ReactionPicker.tsx +++ b/src/components/middle/message/ReactionPicker.tsx @@ -33,8 +33,8 @@ interface StateProps { position?: IAnchorPosition; } -const FULL_PICKER_SHIFT_DELTA = { x: -30, y: -66 }; -const LIMITED_PICKER_SHIFT_DELTA = { x: -25, y: -10 }; +const FULL_PICKER_SHIFT_DELTA = { x: -23, y: -64 }; +const LIMITED_PICKER_SHIFT_DELTA = { x: -21, y: -10 }; const ReactionPicker: FC = ({ isOpen, @@ -44,13 +44,6 @@ const ReactionPicker: FC = ({ }) => { const { toggleReaction, closeReactionPicker } = getActions(); - // eslint-disable-next-line no-null/no-null - const scrollHeaderRef = useRef(null); - // eslint-disable-next-line no-null/no-null - const scrollContainerRef = useRef(null); - // eslint-disable-next-line no-null/no-null - const limitedScrollContainerRef = useRef(null); - const renderedMessageId = useCurrentOrPrev(message?.id, true); const renderedChatId = useCurrentOrPrev(message?.chatId, true); const storedPosition = useCurrentOrPrev(position, true); @@ -73,18 +66,6 @@ const ReactionPicker: FC = ({ positionX, positionY, transformOriginX, transformOriginY, style, } = useMenuPosition(renderingPosition, getTriggerElement, getRootElement, getMenuElement, getLayout); - const handleResetScrollPosition = useCallback(() => { - if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTop = 0; - } - if (scrollHeaderRef.current) { - scrollHeaderRef.current.scrollLeft = 0; - } - if (limitedScrollContainerRef.current) { - limitedScrollContainerRef.current.scrollTop = 0; - } - }, []); - const handleToggleCustomReaction = useCallback((sticker: ApiSticker) => { if (!renderedChatId || !renderedMessageId) { return; @@ -120,17 +101,12 @@ const ReactionPicker: FC = ({ }, []); }, [message?.reactions?.results]); - const bubbleFullClassName = buildClassName( - styles.menuContent, - !withCustomReactions && styles.onlyReactions, - ); - return ( = ({ transformOriginX={transformOriginX} transformOriginY={transformOriginY} style={style} + backdropExcludedSelector=".Modal.confirm" onClose={closeReactionPicker} - onCloseAnimationEnd={handleResetScrollPosition} > {!withCustomReactions && Boolean(renderedChatId) && ( diff --git a/src/components/middle/message/ReactionPickerLimited.module.scss b/src/components/middle/message/ReactionPickerLimited.module.scss index 39294c367..3cf1d0af3 100644 --- a/src/components/middle/message/ReactionPickerLimited.module.scss +++ b/src/components/middle/message/ReactionPickerLimited.module.scss @@ -1,11 +1,11 @@ .root { - --emoji-size: 2.5rem; + --emoji-size: 2.25rem; } .wrapper { position: relative; height: auto; - max-height: 18rem; + max-height: min(18rem, 100%); overflow-y: auto; padding: 0.5rem 0.25rem; } diff --git a/src/components/middle/message/ReactionPickerLimited.tsx b/src/components/middle/message/ReactionPickerLimited.tsx index f7b7558eb..b5185c2d4 100644 --- a/src/components/middle/message/ReactionPickerLimited.tsx +++ b/src/components/middle/message/ReactionPickerLimited.tsx @@ -1,4 +1,3 @@ -import type { RefObject } from 'react'; import React, { memo, useRef, useMemo } from '../../../lib/teact/teact'; import { withGlobal } from '../../../global'; @@ -18,7 +17,6 @@ import ReactionEmoji from '../../common/ReactionEmoji'; import styles from './ReactionPickerLimited.module.scss'; type OwnProps = { - scrollContainerRef?: RefObject; chatId: string; loadAndPlay: boolean; onReactionSelect?: (reaction: ApiReaction) => void; @@ -34,7 +32,7 @@ type StateProps = { isCurrentUserPremium?: boolean; }; -const REACTION_SIZE = 40; +const REACTION_SIZE = 36; const GRID_GAP_THRESHOLD = 600; const MODAL_PADDING_SIZE_REM = 0.5; const MODAL_MAX_HEIGHT_REM = 18; @@ -43,7 +41,6 @@ const GRID_GAP_DESKTOP_REM = 0.625; const GRID_GAP_MOBILE_REM = 0.5; const ReactionPickerLimited: FC = ({ - scrollContainerRef, loadAndPlay, enabledReactions, availableReactions, @@ -51,17 +48,12 @@ const ReactionPickerLimited: FC = ({ selectedReactionIds, onReactionSelect, }) => { - // eslint-disable-next-line no-null/no-null - let containerRef = useRef(null); // eslint-disable-next-line no-null/no-null const sharedCanvasRef = useRef(null); // eslint-disable-next-line no-null/no-null const sharedCanvasHqRef = useRef(null); const { width: windowWidth } = useWindowSize(); const { isTouchScreen } = useAppLayout(); - if (scrollContainerRef) { - containerRef = scrollContainerRef; - } const allAvailableReactions = useMemo(() => { if (!enabledReactions) { @@ -83,17 +75,14 @@ const ReactionPickerLimited: FC = ({ const itemsInRow = Math.floor((availableWidth + gapWidth) / (REACTION_SIZE + gapWidth)); const rowsCount = Math.ceil(allAvailableReactions.length / itemsInRow); - const pickerMaxHeight = rowsCount * REACTION_SIZE + (rowsCount + 1) * gapWidth + MODAL_PADDING_SIZE_REM * REM; + const pickerMaxHeight = rowsCount * REACTION_SIZE + (rowsCount - 1) * gapWidth + MODAL_PADDING_SIZE_REM * REM * 2; return Math.min(MODAL_MAX_HEIGHT_REM * REM, pickerMaxHeight); }, [allAvailableReactions.length, windowWidth]); return (
-
+
diff --git a/src/components/middle/message/ReactionSelector.scss b/src/components/middle/message/ReactionSelector.scss index 7c192b610..b96f061d8 100644 --- a/src/components/middle/message/ReactionSelector.scss +++ b/src/components/middle/message/ReactionSelector.scss @@ -20,21 +20,12 @@ justify-content: center; } - &--withBlur { - background: none; - filter: none; + &__bubble-big, + &__bubble-small, + &__items-wrapper { + background: var(--color-background-compact-menu); + backdrop-filter: blur(10px); - .ReactionSelector__bubble-big, - .ReactionSelector__bubble-small, - .ReactionSelector__items-wrapper { - background: var(--color-background-compact-menu); - backdrop-filter: blur(10px); - } - } - - .ReactionSelector__bubble-big, - .ReactionSelector__bubble-small, - .ReactionSelector__items-wrapper { filter: drop-shadow(0 0.25rem 0.125rem var(--color-default-shadow)); body.is-safari & { @@ -44,7 +35,6 @@ } &__bubble-big { - background: var(--color-background); position: absolute; display: block; content: ""; @@ -77,7 +67,6 @@ width: 0.5rem; height: 0.5rem; border-radius: 50%; - background: var(--color-background); &--isRtl { right: auto; @@ -93,7 +82,6 @@ width: 100%; height: 100%; border-radius: 3rem; - background: var(--color-background); @media (max-width: 600px) { width: fit-content; diff --git a/src/components/middle/message/ReactionSelector.tsx b/src/components/middle/message/ReactionSelector.tsx index fec81e191..bc15fcafa 100644 --- a/src/components/middle/message/ReactionSelector.tsx +++ b/src/components/middle/message/ReactionSelector.tsx @@ -12,7 +12,6 @@ import { createClassNameBuilder } from '../../../util/buildClassName'; import { isSameReaction, canSendReaction, getReactionUniqueKey, sortReactions, } from '../../../global/helpers'; -import useAppLayout from '../../../hooks/useAppLayout'; import useLang from '../../../hooks/useLang'; import ReactionSelectorReaction from './ReactionSelectorReaction'; @@ -50,7 +49,6 @@ const ReactionSelector: FC = ({ }) => { // eslint-disable-next-line no-null/no-null const ref = useRef(null); - const { isTouchScreen } = useAppLayout(); const lang = useLang(); const availableReactions = useMemo(() => { @@ -94,7 +92,7 @@ const ReactionSelector: FC = ({ if (!reactionsToRender.length) return undefined; return ( -
+
diff --git a/src/components/right/CreateTopic.tsx b/src/components/right/CreateTopic.tsx index accff316a..7182b0c0e 100644 --- a/src/components/right/CreateTopic.tsx +++ b/src/components/right/CreateTopic.tsx @@ -135,6 +135,7 @@ const CreateTopic: FC = ({
= ({
) => void; onCloseAnimationEnd?: () => void; @@ -68,6 +69,7 @@ const Menu: FC = ({ autoClose = false, footer, noCloseOnBackdrop = false, + backdropExcludedSelector, noCompact, onCloseAnimationEnd, onClose, @@ -118,6 +120,8 @@ const Menu: FC = ({ isOpen, backdropContainerRef, noCloseOnBackdrop ? undefined : onClose, + undefined, + backdropExcludedSelector, ); const bubbleFullClassName = buildClassName( diff --git a/src/components/ui/Modal.scss b/src/components/ui/Modal.scss index dc2676c81..8957725a0 100644 --- a/src/components/ui/Modal.scss +++ b/src/components/ui/Modal.scss @@ -3,7 +3,7 @@ z-index: var(--z-modal); &.confirm { - z-index: var(--z-lock-screen); + z-index: var(--z-modal-confirm); } &.delete, diff --git a/src/config.ts b/src/config.ts index 809a13f7c..fa7d330aa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -161,15 +161,15 @@ export const STICKER_SIZE_INLINE_DESKTOP_FACTOR = 13; export const STICKER_SIZE_INLINE_MOBILE_FACTOR = 11; export const STICKER_SIZE_AUTH = 160; export const STICKER_SIZE_AUTH_MOBILE = 120; -export const STICKER_SIZE_PICKER = 64; -export const EMOJI_SIZE_PICKER = 40; +export const STICKER_SIZE_PICKER = 72; +export const EMOJI_SIZE_PICKER = 36; export const COMPOSER_EMOJI_SIZE_PICKER = 32; export const STICKER_SIZE_GENERAL_SETTINGS = 48; export const STICKER_SIZE_PICKER_HEADER = 32; export const STICKER_PICKER_MAX_SHARED_COVERS = 20; -export const STICKER_SIZE_SEARCH = 64; -export const STICKER_SIZE_MODAL = 64; -export const EMOJI_SIZE_MODAL = 40; +export const STICKER_SIZE_SEARCH = 72; +export const STICKER_SIZE_MODAL = 72; +export const EMOJI_SIZE_MODAL = 36; export const STICKER_SIZE_TWO_FA = 160; export const STICKER_SIZE_PASSCODE = 160; export const STICKER_SIZE_DISCUSSION_GROUPS = 140; diff --git a/src/global/actions/api/reactions.ts b/src/global/actions/api/reactions.ts index cbafe348d..6fd719bf2 100644 --- a/src/global/actions/api/reactions.ts +++ b/src/global/actions/api/reactions.ts @@ -20,7 +20,9 @@ import { updateTabState } from '../../reducers/tabs'; import * as mediaLoader from '../../../util/mediaLoader'; import { buildCollectionByKey, omit } from '../../../util/iteratees'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; -import { isSameReaction, getUserReactions, isMessageLocal } from '../../helpers'; +import { + isSameReaction, getUserReactions, isMessageLocal, getDocumentMediaHash, +} from '../../helpers'; const INTERACTION_RANDOM_OFFSET = 40; @@ -43,6 +45,9 @@ addActionHandler('loadAvailableReactions', async (global): Promise => { if (availableReaction.appearAnimation) { mediaLoader.fetch(`sticker${availableReaction.appearAnimation.id}`, ApiMediaFormat.BlobUrl); } + if (availableReaction.selectAnimation) { + mediaLoader.fetch(getDocumentMediaHash(availableReaction.selectAnimation), ApiMediaFormat.BlobUrl); + } }); global = getGlobal(); diff --git a/src/hooks/useVirtualBackdrop.ts b/src/hooks/useVirtualBackdrop.ts index 290899f0a..0139e6f02 100644 --- a/src/hooks/useVirtualBackdrop.ts +++ b/src/hooks/useVirtualBackdrop.ts @@ -10,8 +10,13 @@ export default function useVirtualBackdrop( menuRef: RefObject, onClose?: () => void | undefined, ignoreRightClick?: boolean, + excludedClosestSelector?: string, ) { useEffect(() => { + if (!isOpen || !onClose) { + return undefined; + } + const handleEvent = (e: MouseEvent) => { const menu = menuRef.current; const target = e.target as HTMLElement | null; @@ -19,22 +24,22 @@ export default function useVirtualBackdrop( return; } - if ( + if (( !menu.contains(e.target as Node | null) || target.classList.contains(BACKDROP_CLASSNAME) - ) { + ) && !(excludedClosestSelector && ( + target.matches(excludedClosestSelector) || target.closest(excludedClosestSelector) + ))) { e.preventDefault(); e.stopPropagation(); onClose?.(); } }; - if (isOpen && onClose) { - document.addEventListener('mousedown', handleEvent); - } + document.addEventListener('mousedown', handleEvent); return () => { document.removeEventListener('mousedown', handleEvent); }; - }, [ignoreRightClick, isOpen, menuRef, onClose]); + }, [excludedClosestSelector, ignoreRightClick, isOpen, menuRef, onClose]); } diff --git a/src/lib/rlottie/RLottie.ts b/src/lib/rlottie/RLottie.ts index 9c0fc0321..e0a154631 100644 --- a/src/lib/rlottie/RLottie.ts +++ b/src/lib/rlottie/RLottie.ts @@ -24,7 +24,7 @@ type Frame = | typeof WAITING | ImageBitmap; -const MAX_WORKERS = 4; +const MAX_WORKERS = Math.min(navigator.hardwareConcurrency || 4, 4); const HIGH_PRIORITY_QUALITY = (IS_ANDROID || IS_IOS) ? 0.75 : 1; const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75; const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24; diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index ae3177685..25002e7d3 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -88,6 +88,7 @@ $color-message-reaction-own-hover: #b5e0a4; --color-text-green-rgb: #{toRGB($color-text-green)}; --color-borders: #{$color-borders}; --color-borders-input: #{$color-borders}; + --color-borders-alternate: rgba(0, 0, 0, 0.1); --color-dividers: #{$color-dividers}; --color-dividers-android: #{$color-dividers-android}; --color-webpage-initial-background: #{$color-dark-gray}; @@ -197,8 +198,8 @@ $color-message-reaction-own-hover: #b5e0a4; --custom-emoji-size: 1.25rem; --custom-emoji-border-radius: 0; - --symbol-menu-width: 26.25rem; - --symbol-menu-height: 23.25rem; + --symbol-menu-width: 24rem; + --symbol-menu-height: 22.375rem; --symbol-menu-footer-height: 3rem; @media (min-width: 1276px) and (max-width: 1920px) { @@ -212,14 +213,17 @@ $color-message-reaction-own-hover: #b5e0a4; @media (max-width: 600px) { --right-column-width: 100vw; --symbol-menu-width: 100vw; - --symbol-menu-height: 14.6875rem; + --symbol-menu-height: 17.6875rem; } + --z-modal-confirm: 10000; + --z-symbol-menu-modal: 5000; --z-lock-screen: 3000; --z-ui-loader-mask: 2000; --z-notification: 1700; --z-confetti: 1600; --z-reaction-picker: 1200; + --z-reaction-interaction-effect: 1000; --z-right-column: 900; --z-header-menu: 990; --z-header-menu-backdrop: 980; diff --git a/src/styles/index.scss b/src/styles/index.scss index 31d98921a..b3cd46457 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -278,7 +278,8 @@ body:not(.is-ios) { position: relative; } -.shared-canvas, .absolute-video-container { +.shared-canvas, +.absolute-video-container { position: absolute; top: 0; left: 0;