feat(expressions): pickers and upload for emojis and stickers
Completes the feature: the tables existed but nothing could be put in them. Space settings gain an Emojis & Stickers panel behind MANAGE_SPACE, with a 512KB ceiling — both are fetched on every message that uses them, so weight matters more than fidelity. The suggested name is pre-normalised so the common case needs no typing, and a name collision reports itself distinctly from an upload failure: the corrective action is different. Custom emojis join the emoji picker as their own category. They have no native character, so selecting one inserts :name: — the same text the renderer resolves back to an image, which also means copying a message yields something that still reads. Stickers get a picker tab that only appears inside a space, since that is where they exist, and send immediately on click: a sticker is the whole message, so parking it in the composer to await Enter would make no sense. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import React, { useRef, useEffect, useCallback } from 'react';
|
||||
import { useT } from '../../i18n';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { EmojiPicker } from './EmojiPicker';
|
||||
import { GifPicker } from './GifPicker';
|
||||
import { StickerPicker } from './StickerPicker';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { useDragToClose } from '../../hooks/useDragToClose';
|
||||
|
||||
export type InputPopoverTab = 'emoji' | 'gif';
|
||||
export type InputPopoverTab = 'emoji' | 'gif' | 'sticker';
|
||||
|
||||
interface InputPopoverProps {
|
||||
activeTab: InputPopoverTab;
|
||||
@@ -15,6 +17,8 @@ interface InputPopoverProps {
|
||||
anchorRef: React.RefObject<HTMLElement | null>;
|
||||
gifEnabled: boolean;
|
||||
onTabChange: (tab: InputPopoverTab) => void;
|
||||
onStickerSelect: (stickerId: string) => void;
|
||||
hasStickers?: boolean;
|
||||
}
|
||||
|
||||
interface SharedTabProps {
|
||||
@@ -53,6 +57,7 @@ function DesktopPopover({
|
||||
onClose,
|
||||
onEmojiSelect,
|
||||
onGifSelect,
|
||||
onStickerSelect,
|
||||
anchorRef,
|
||||
gifEnabled,
|
||||
onTabChange,
|
||||
@@ -139,6 +144,7 @@ function DesktopPopover({
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
{activeTab === 'emoji' && <EmojiPicker onEmojiSelect={onEmojiSelect} />}
|
||||
{activeTab === 'gif' && gifEnabled && <GifPicker onGifSelect={onGifSelect} />}
|
||||
{activeTab === 'sticker' && <StickerPicker onStickerSelect={onStickerSelect} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
@@ -155,6 +161,7 @@ function MobileSheet({
|
||||
onClose,
|
||||
onEmojiSelect,
|
||||
onGifSelect,
|
||||
onStickerSelect,
|
||||
gifEnabled,
|
||||
onTabChange,
|
||||
availableTabs,
|
||||
@@ -221,6 +228,7 @@ function MobileSheet({
|
||||
<div className="flex-1 min-h-0 overflow-hidden flex flex-col">
|
||||
{activeTab === 'emoji' && <EmojiPicker onEmojiSelect={onEmojiSelect} mobile />}
|
||||
{activeTab === 'gif' && gifEnabled && <GifPicker onGifSelect={onGifSelect} mobile />}
|
||||
{activeTab === 'sticker' && <StickerPicker onStickerSelect={onStickerSelect} mobile />}
|
||||
</div>
|
||||
</div>
|
||||
</>,
|
||||
@@ -231,12 +239,17 @@ function MobileSheet({
|
||||
export function InputPopover(props: InputPopoverProps) {
|
||||
const isMobile = useUIStore((s) => s.isMobile);
|
||||
|
||||
const t = useT();
|
||||
const availableTabs: { key: InputPopoverTab; label: string }[] = [
|
||||
{ key: 'emoji', label: 'Emoji' },
|
||||
];
|
||||
if (props.gifEnabled) {
|
||||
availableTabs.splice(0, 0, { key: 'gif', label: 'GIF' });
|
||||
}
|
||||
// Figurinha só existe dentro de um servidor; em DM a aba não aparece.
|
||||
if (props.hasStickers) {
|
||||
availableTabs.push({ key: 'sticker', label: t('expressions.stickers') });
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
return <MobileSheet {...props} availableTabs={availableTabs} />;
|
||||
|
||||
Reference in New Issue
Block a user