diff --git a/src/assets/reaction-thumbs.png b/src/assets/reaction-thumbs.png new file mode 100644 index 00000000..b31a8d1b Binary files /dev/null and b/src/assets/reaction-thumbs.png differ diff --git a/src/components/middle/message/ReactionSelector.scss b/src/components/middle/message/ReactionSelector.scss index 6438ad76..43adb5b0 100644 --- a/src/components/middle/message/ReactionSelector.scss +++ b/src/components/middle/message/ReactionSelector.scss @@ -10,7 +10,7 @@ right: -3rem; top: -3.5rem; - .bubble-big { + &__bubble-big { position: absolute; display: block; content: ""; @@ -23,7 +23,7 @@ z-index: -1; } - .bubble-small { + &__bubble-small { position: absolute; display: block; content: ""; @@ -40,18 +40,19 @@ box-shadow: 0 0.25rem 0.125rem var(--color-default-shadow); } - body.is-safari .bubble-small, body.is-safari .bubble-big { + body.is-safari &__bubble-small, + body.is-safari &__bubble-big { box-shadow: 0 0.25rem 0.125rem var(--color-default-shadow); } - .items-wrapper { + &__items-wrapper { width: 100%; height: 100%; overflow: hidden; border-radius: 3rem; } - .items { + &__items { padding: 0 1rem; width: 100%; height: 100%; @@ -63,29 +64,4 @@ align-items: center; border-radius: 3rem; } - - .reaction { - margin-left: 0.5rem; - position: relative; - min-width: 2rem; - min-height: 2rem; - - &:first-child { - margin-left: 0; - } - } - - .ReactionStaticEmoji { - width: 2rem; - position: absolute; - top: 0; - left: 0; - transform: scale(0.9); - } - - .AnimatedSticker { - position: absolute; - top: 0; - left: 0; - } } diff --git a/src/components/middle/message/ReactionSelector.tsx b/src/components/middle/message/ReactionSelector.tsx index 67b5ed93..b6d61ea0 100644 --- a/src/components/middle/message/ReactionSelector.tsx +++ b/src/components/middle/message/ReactionSelector.tsx @@ -2,21 +2,16 @@ import React, { FC, memo, useLayoutEffect, useRef, } from '../../../lib/teact/teact'; -import { ApiAvailableReaction, ApiMediaFormat } from '../../../api/types'; +import { ApiAvailableReaction } from '../../../api/types'; -import useMedia from '../../../hooks/useMedia'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useFlag from '../../../hooks/useFlag'; -import useShowTransition from '../../../hooks/useShowTransition'; import { getTouchY } from '../../../util/scrollLock'; - -import ReactionStaticEmoji from '../../common/ReactionStaticEmoji'; -import AnimatedSticker from '../../common/AnimatedSticker'; +import { createClassNameBuilder } from '../../../util/buildClassName'; +import ReactionSelectorReaction from './ReactionSelectorReaction'; import './ReactionSelector.scss'; -const REACTION_SIZE = 32; - type OwnProps = { enabledReactions?: string[]; onSendReaction: (reaction: string, x: number, y: number) => void; @@ -25,57 +20,8 @@ type OwnProps = { isReady?: boolean; }; -const AvailableReaction: FC<{ - reaction: ApiAvailableReaction; - isReady?: boolean; - onSendReaction: (reaction: string, x: number, y: number) => void; -}> = ({ reaction, onSendReaction, isReady }) => { - // eslint-disable-next-line no-null/no-null - const containerRef = useRef(null); +const cn = createClassNameBuilder('ReactionSelector'); - const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady, ApiMediaFormat.Lottie); - - const [isActivated, activate, deactivate] = useFlag(); - const [isAnimationLoaded, markAnimationLoaded] = useFlag(); - - const shouldRenderAnimated = Boolean(isReady && mediaData); - const { transitionClassNames: animatedClassNames } = useShowTransition(shouldRenderAnimated); - const { shouldRender: shouldRenderStatic, transitionClassNames: staticClassNames } = useShowTransition( - !isReady || !isAnimationLoaded, undefined, true, - ); - - function handleClick() { - if (!containerRef.current) return; - const { x, y } = containerRef.current.getBoundingClientRect(); - - onSendReaction(reaction.reaction, x, y); - } - - return ( -
- {shouldRenderStatic && ( - - )} - {shouldRenderAnimated && ( - - )} -
- ); -}; const ReactionSelector: FC = ({ availableReactions, enabledReactions, @@ -104,16 +50,16 @@ const ReactionSelector: FC = ({ if ((!isPrivate && !enabledReactions?.length) || !availableReactions) return undefined; return ( -
-
-
-
-
+
+
+
+
+
{availableReactions?.map((reaction) => { if (reaction.isInactive || (!isPrivate && (!enabledReactions || !enabledReactions.includes(reaction.reaction)))) return undefined; return ( - void; +}; + +const cn = createClassNameBuilder('ReactionSelectorReaction'); + +const ReactionSelectorReaction: FC = ({ reaction, onSendReaction, isReady }) => { + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + + const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady, ApiMediaFormat.Lottie); + + const [isActivated, activate, deactivate] = useFlag(); + const [isAnimationLoaded, markAnimationLoaded] = useFlag(); + + const shouldRenderAnimated = Boolean(isReady && mediaData); + const { transitionClassNames: animatedClassNames } = useShowTransition(shouldRenderAnimated); + const { shouldRender: shouldRenderStatic, transitionClassNames: staticClassNames } = useShowTransition( + !isReady || !isAnimationLoaded, undefined, true, + ); + + function handleClick() { + if (!containerRef.current) return; + const { x, y } = containerRef.current.getBoundingClientRect(); + + onSendReaction(reaction.reaction, x, y); + } + + return ( +
+ {shouldRenderStatic && ( +
+ )} + {shouldRenderAnimated && ( + + )} +
+ ); +}; + +export default memo(ReactionSelectorReaction); diff --git a/src/util/buildClassName.ts b/src/util/buildClassName.ts index 886a7882..95eafe29 100644 --- a/src/util/buildClassName.ts +++ b/src/util/buildClassName.ts @@ -1,16 +1,22 @@ type Parts = (string | false | undefined)[]; +type PartsWithGlobals = (string | false | undefined | string[])[]; export default function buildClassName(...parts: Parts) { return parts.filter(Boolean).join(' '); } export function createClassNameBuilder(componentName: string) { - return (elementName: string, ...modifiers: Parts) => { + return (elementName: string, ...modifiers: PartsWithGlobals) => { const baseName = elementName === '&' ? componentName : `${componentName}__${elementName}`; - return modifiers.reduce((acc, modifier) => { + return modifiers.reduce((acc, modifier) => { if (modifier) { - acc.push(`${baseName}--${modifier}`); + // A bit hacky way to pass global class names + if (Array.isArray(modifier)) { + acc.push(...modifier); + } else { + acc.push(`${baseName}--${modifier}`); + } } return acc;