From f6744829118d07b2c68f8cd961084c3f1d98ef1a Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 13 Sep 2025 09:41:34 +0530 Subject: [PATCH] create emoji/sticker preview atom --- .../components/emoji-board/EmojiBoard.css.tsx | 21 --- src/app/components/emoji-board/EmojiBoard.tsx | 128 ++++++------------ .../emoji-board/components/Preview.tsx | 53 ++++++++ .../emoji-board/components/index.tsx | 1 + .../emoji-board/components/styles.css.ts | 31 ++++- 5 files changed, 124 insertions(+), 110 deletions(-) create mode 100644 src/app/components/emoji-board/components/Preview.tsx diff --git a/src/app/components/emoji-board/EmojiBoard.css.tsx b/src/app/components/emoji-board/EmojiBoard.css.tsx index 20af52e7..3411dc78 100644 --- a/src/app/components/emoji-board/EmojiBoard.css.tsx +++ b/src/app/components/emoji-board/EmojiBoard.css.tsx @@ -24,17 +24,6 @@ export const Header = style({ paddingBottom: 0, }); -export const Footer = style({ - padding: config.space.S200, - margin: config.space.S300, - marginTop: 0, - minHeight: toRem(40), - - borderRadius: config.radii.R400, - backgroundColor: color.SurfaceVariant.Container, - color: color.SurfaceVariant.OnContainer, -}); - export const EmojiGroup = style({ padding: `${config.space.S300} 0`, }); @@ -58,16 +47,6 @@ export const EmojiGroupContent = style([ }, ]); -export const EmojiPreview = style([ - DefaultReset, - { - width: toRem(32), - height: toRem(32), - fontSize: toRem(32), - lineHeight: toRem(32), - }, -]); - export const EmojiItem = style([ DefaultReset, FocusOutline, diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index c72a418d..8044f217 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -42,6 +42,9 @@ import { SidebarBtn, Sidebar, NoStickerPacks, + createPreviewDataAtom, + Preview, + PreviewData, } from './components'; import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types'; @@ -68,35 +71,14 @@ const getEmojiItemInfo = (element: Element): EmojiItemInfo | undefined => { const activeGroupIdAtom = atom(undefined); -function Header({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} - -function Content({ children }: { children: ReactNode }) { - return {children}; -} - -function Footer({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} - const EmojiBoardLayout = as< 'div', { header: ReactNode; sidebar?: ReactNode; - footer?: ReactNode; children: ReactNode; } ->(({ className, header, sidebar, footer, children, ...props }, ref) => ( +>(({ className, header, sidebar, children, ...props }, ref) => ( - {header} + + {header} + {children} - {footer} {sidebar} @@ -439,10 +422,11 @@ export const StickerGroups = memo( mx: MatrixClient; groups: ImagePack[]; useAuthentication?: boolean; - }) => ( - <> - {groups.length === 0 && } - {groups.map((pack) => ( + }) => + groups.length === 0 ? ( + + ) : ( + groups.map((pack) => ( {pack .getImages(ImageUsage.Sticker) @@ -464,9 +448,8 @@ export const StickerGroups = memo( ))} - ))} - - ) + )) + ) ); export const NativeEmojiGroups = memo( @@ -491,6 +474,8 @@ export const NativeEmojiGroups = memo( ) ); +const DefaultEmojiPreview: PreviewData = { key: '🙂', shortcode: 'slight_smile' }; + const SEARCH_OPTIONS: UseAsyncSearchOptions = { limit: 1000, matchOptions: { @@ -525,6 +510,11 @@ export function EmojiBoard({ const stickerTab = tab === EmojiBoardTab.Sticker; const usage = emojiTab ? ImageUsage.Emoticon : ImageUsage.Sticker; + const previewAtom = useMemo( + () => createPreviewDataAtom(emojiTab ? DefaultEmojiPreview : undefined), + [emojiTab] + ); + const setPreviewData = useSetAtom(previewAtom); const setActiveGroupId = useSetAtom(activeGroupIdAtom); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); @@ -534,8 +524,6 @@ export function EmojiBoard({ const recentEmojis = useRecentEmoji(mx, 21); const contentScrollRef = useRef(null); - const emojiPreviewRef = useRef(null); - const emojiPreviewTextRef = useRef(null); const searchList = useMemo(() => { let list: Array = []; @@ -615,23 +603,14 @@ export function EmojiBoard({ const handleEmojiPreview = useCallback( (element: HTMLButtonElement) => { const emojiInfo = getEmojiItemInfo(element); - if (!emojiInfo || !emojiPreviewTextRef.current) return; - if (emojiInfo.type === EmojiType.Emoji && emojiPreviewRef.current) { - emojiPreviewRef.current.textContent = emojiInfo.data; - } else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) { - const img = document.createElement('img'); - img.className = css.CustomEmojiImg; - img.setAttribute( - 'src', - mxcUrlToHttp(mx, emojiInfo.data, useAuthentication) || emojiInfo.data - ); - img.setAttribute('alt', emojiInfo.shortcode); - emojiPreviewRef.current.textContent = ''; - emojiPreviewRef.current.appendChild(img); - } - emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`; + if (!emojiInfo) return; + + setPreviewData({ + key: emojiInfo.data, + shortcode: emojiInfo.shortcode, + }); }, - [mx, useAuthentication] + [setPreviewData] ); const throttleEmojiHover = useThrottle(handleEmojiPreview, { @@ -675,17 +654,15 @@ export function EmojiBoard({ > - - {onTabChange && } - - - + + {onTabChange && } + + } sidebar={ @@ -711,34 +688,8 @@ export function EmojiBoard({ )} } - footer={ - emojiTab ? ( -
- - 😃 - - - :smiley: - -
- ) : ( - imagePacks.length > 0 && ( -
- - :smiley: - -
- ) - ) - } > - + } - +
+ ); diff --git a/src/app/components/emoji-board/components/Preview.tsx b/src/app/components/emoji-board/components/Preview.tsx new file mode 100644 index 00000000..3f5f8d3a --- /dev/null +++ b/src/app/components/emoji-board/components/Preview.tsx @@ -0,0 +1,53 @@ +import { Box, Text } from 'folds'; +import React from 'react'; +import { Atom, atom, useAtomValue } from 'jotai'; +import * as css from './styles.css'; +import { useMatrixClient } from '../../../hooks/useMatrixClient'; +import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; +import { mxcUrlToHttp } from '../../../utils/matrix'; + +export type PreviewData = { + key: string; + shortcode: string; +}; + +export const createPreviewDataAtom = (initial?: PreviewData) => + atom(initial); + +type PreviewProps = { + previewAtom: Atom; +}; +export function Preview({ previewAtom }: PreviewProps) { + const mx = useMatrixClient(); + const useAuthentication = useMediaAuthentication(); + + const { key, shortcode } = useAtomValue(previewAtom) ?? {}; + + if (!shortcode) return null; + + return ( + + {key && ( + + {key.startsWith('mxc://') ? ( + {shortcode} + ) : ( + key + )} + + )} + + :{shortcode}: + + + ); +} diff --git a/src/app/components/emoji-board/components/index.tsx b/src/app/components/emoji-board/components/index.tsx index 7d4b4b48..88ecbf23 100644 --- a/src/app/components/emoji-board/components/index.tsx +++ b/src/app/components/emoji-board/components/index.tsx @@ -2,3 +2,4 @@ export * from './SearchInput'; export * from './Tabs'; export * from './Sidebar'; export * from './NoStickerPacks'; +export * from './Preview'; diff --git a/src/app/components/emoji-board/components/styles.css.ts b/src/app/components/emoji-board/components/styles.css.ts index 5855d51a..50d6ec44 100644 --- a/src/app/components/emoji-board/components/styles.css.ts +++ b/src/app/components/emoji-board/components/styles.css.ts @@ -1,5 +1,5 @@ import { style } from '@vanilla-extract/css'; -import { toRem, color, config } from 'folds'; +import { toRem, color, config, DefaultReset } from 'folds'; export const Sidebar = style({ width: toRem(54), @@ -20,3 +20,32 @@ export const SidebarStack = style({ export const SidebarDivider = style({ width: toRem(18), }); + +export const Preview = style({ + padding: config.space.S200, + margin: config.space.S300, + marginTop: 0, + minHeight: toRem(40), + + borderRadius: config.radii.R400, + backgroundColor: color.SurfaceVariant.Container, + color: color.SurfaceVariant.OnContainer, +}); + +export const PreviewEmoji = style([ + DefaultReset, + { + width: toRem(32), + height: toRem(32), + fontSize: toRem(32), + lineHeight: toRem(32), + }, +]); +export const PreviewImg = style([ + DefaultReset, + { + width: toRem(32), + height: toRem(32), + objectFit: 'contain', + }, +]);