Paket A: Vollständige Emoji-Auswahl für Notiz-Icons mit Lazy-Loading und Mobile Bottom-Sheet
This commit is contained in:
1 parent
a354f3d008
commit
633bd6f1ef
5 files changed
+281
-131
No files matched your search
Generated
+22
@@ -9,6 +9,7 @@
|
||||
"version": "0.2.0",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.6",
|
||||
"emoji-picker-react": "^4.12.0",
|
||||
"framer-motion": "^11.15.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"prismjs": "^1.30.0",
|
||||
@@ -2101,6 +2102,21 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emoji-picker-react": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.12.0.tgz",
|
||||
"integrity": "sha512-q2c8UcZH0eRIMj41bj0k1akTjk69tsu+E7EzkW7giN66iltF6H9LQvQvw6ugscsxdC+1lmt3WZpQkkY65J95tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"flairup": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
@@ -2302,6 +2318,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/flairup": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flairup/-/flairup-1.0.0.tgz",
|
||||
"integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.6",
|
||||
"emoji-picker-react": "4.12.0",
|
||||
"framer-motion": "^11.15.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"prismjs": "^1.30.0",
|
||||
|
||||
@@ -44,63 +44,7 @@ export interface DocumentEditorProps {
|
||||
onSaveStatusChange?: (status: DocSaveStatus, errorMsg?: string) => void;
|
||||
}
|
||||
|
||||
interface EmojiItem {
|
||||
emoji: string;
|
||||
category: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
const EMOJI_CATALOG: EmojiItem[] = [
|
||||
// Notizen & Dokumente
|
||||
{ emoji: '📝', category: 'Notizen', keywords: 'notiz schreiben memo text note' },
|
||||
{ emoji: '📄', category: 'Notizen', keywords: 'dokument seite page doc' },
|
||||
{ emoji: '📋', category: 'Notizen', keywords: 'clipboard klemmbrett liste' },
|
||||
{ emoji: '📂', category: 'Notizen', keywords: 'ordner mappe folder datei' },
|
||||
{ emoji: '📁', category: 'Notizen', keywords: 'ordner folder directory' },
|
||||
{ emoji: '📌', category: 'Notizen', keywords: 'pin anheften bookmark wichtig' },
|
||||
{ emoji: '📍', category: 'Notizen', keywords: 'standort ort pin location' },
|
||||
{ emoji: '🏷️', category: 'Notizen', keywords: 'tag etikett label markierung' },
|
||||
{ emoji: '📚', category: 'Notizen', keywords: 'buch bibliothek lesen studium' },
|
||||
{ emoji: '📖', category: 'Notizen', keywords: 'buch open book wissen' },
|
||||
|
||||
// Ideen & Fokus
|
||||
{ emoji: '🧠', category: 'Ideen', keywords: 'gehirn brain denken wissen thought mind' },
|
||||
{ emoji: '💡', category: 'Ideen', keywords: 'idee inspiration lampe glühbirne idea' },
|
||||
{ emoji: '🎯', category: 'Ideen', keywords: 'ziel target fokus mission okr' },
|
||||
{ emoji: '🚀', category: 'Ideen', keywords: 'rakete start launch projekt rocket' },
|
||||
{ emoji: '⚡', category: 'Ideen', keywords: 'blitz energie schnell quick power' },
|
||||
{ emoji: '🔥', category: 'Ideen', keywords: 'feuer flame priorität hot wichtig' },
|
||||
{ emoji: '⭐', category: 'Ideen', keywords: 'stern favoriten star top' },
|
||||
{ emoji: '✨', category: 'Ideen', keywords: 'funkeln magie neu neuigkeit sparkles' },
|
||||
{ emoji: '🧩', category: 'Ideen', keywords: 'puzzle teil lösung problem' },
|
||||
{ emoji: '🔬', category: 'Ideen', keywords: 'mikroskop forschung analyse test' },
|
||||
|
||||
// Tech & Dev
|
||||
{ emoji: '💻', category: 'Tech', keywords: 'laptop computer dev code programming' },
|
||||
{ emoji: '🖥️', category: 'Tech', keywords: 'desktop monitor bildschirm workstation' },
|
||||
{ emoji: '🛠️', category: 'Tech', keywords: 'werkzeug tools optionen settings einstellungen' },
|
||||
{ emoji: '⚙️', category: 'Tech', keywords: 'zahnrad config settings system' },
|
||||
{ emoji: '📊', category: 'Tech', keywords: 'diagramm chart daten statistik dashboard' },
|
||||
{ emoji: '📈', category: 'Tech', keywords: 'wachstum trend kurve business' },
|
||||
{ emoji: '🔒', category: 'Tech', keywords: 'schloss sicherheit geheim privat secure' },
|
||||
{ emoji: '🔑', category: 'Tech', keywords: 'schlüssel key passwort access' },
|
||||
{ emoji: '🌐', category: 'Tech', keywords: 'web internet network welt globus' },
|
||||
{ emoji: '📦', category: 'Tech', keywords: 'paket box release lieferung package' },
|
||||
|
||||
// Alltag & Organisation
|
||||
{ emoji: '💬', category: 'Alltag', keywords: 'chat nachricht kommentar call sprechen' },
|
||||
{ emoji: '✉️', category: 'Alltag', keywords: 'mail email brief post nachricht' },
|
||||
{ emoji: '📅', category: 'Alltag', keywords: 'kalender datum termin plan event' },
|
||||
{ emoji: '⏰', category: 'Alltag', keywords: 'wecker uhr zeit deadline alarm' },
|
||||
{ emoji: '🛒', category: 'Alltag', keywords: 'warenkorb einkauf shopping liste' },
|
||||
{ emoji: '☕', category: 'Alltag', keywords: 'kaffee pause bistro morning' },
|
||||
{ emoji: '🏠', category: 'Alltag', keywords: 'haus heim home wohnung' },
|
||||
{ emoji: '🏢', category: 'Alltag', keywords: 'büro firma arbeit office work' },
|
||||
{ emoji: '✈️', category: 'Alltag', keywords: 'flugzeug reise urlaub trip travel' },
|
||||
{ emoji: '🎨', category: 'Alltag', keywords: 'kunst farbe design creative' },
|
||||
{ emoji: '🏆', category: 'Alltag', keywords: 'pokal erfolg win trophy award' },
|
||||
{ emoji: '✅', category: 'Alltag', keywords: 'haken check done erledigt' },
|
||||
];
|
||||
import { EmojiPickerModal } from './EmojiPickerModal';
|
||||
|
||||
function ensureNonEmptyBlocks(docId: string, blocks?: DocumentBlock[]): DocumentBlock[] {
|
||||
if (!blocks || blocks.length === 0) {
|
||||
@@ -250,21 +194,12 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
const [loading, setLoading] = useState<boolean>(!initialDoc);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false);
|
||||
const [emojiSearch, setEmojiSearch] = useState<string>('');
|
||||
const [zoomedBlockId, setZoomedBlockId] = useState<string | null>(null);
|
||||
|
||||
// Sync engine (operation log): stores every change locally and syncs it with the server
|
||||
const engineRef = useRef<DocSyncEngine | null>(null);
|
||||
const [engineState, setEngineState] = useState<EngineState | null>(null);
|
||||
|
||||
const filteredEmojis = useMemo(() => {
|
||||
const q = emojiSearch.trim().toLowerCase();
|
||||
if (!q) return EMOJI_CATALOG;
|
||||
return EMOJI_CATALOG.filter(
|
||||
(item) => item.keywords.includes(q) || item.emoji.includes(q) || item.category.toLowerCase().includes(q)
|
||||
);
|
||||
}, [emojiSearch]);
|
||||
|
||||
|
||||
const titleInputRef = useRef<HTMLTextAreaElement | HTMLInputElement | null>(null);
|
||||
const outlinerTreeRef = useRef<OutlinerTreeHandle | null>(null);
|
||||
@@ -1044,72 +979,11 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
{doc?.icon || '📝'}
|
||||
</button>
|
||||
{showEmojiPicker && canEdit && (
|
||||
<div
|
||||
className="absolute left-0 mt-1 p-2 panel-surface shadow-lg border border-border rounded-card z-30 w-72 space-y-2"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="relative">
|
||||
<Search className="w-3.5 h-3.5 text-faint absolute left-2 top-1/2 -translate-y-1/2" />
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={emojiSearch}
|
||||
onChange={(e) => setEmojiSearch(e.target.value)}
|
||||
placeholder="Icon suchen oder Emoji eingeben…"
|
||||
className="input-field w-full pl-7 pr-2 py-1 text-xs"
|
||||
<EmojiPickerModal
|
||||
currentIcon={doc?.icon}
|
||||
onSelectEmoji={handleSelectIcon}
|
||||
onClose={() => setShowEmojiPicker(false)}
|
||||
/>
|
||||
</div>
|
||||
{doc?.icon && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSelectIcon('');
|
||||
setShowEmojiPicker(false);
|
||||
setEmojiSearch('');
|
||||
}}
|
||||
className="w-full text-left px-2 py-1 text-status-warn/90 hover:bg-status-warn/10 rounded text-xs font-medium flex items-center gap-1.5 cursor-pointer"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
<span>Icon entfernen</span>
|
||||
</button>
|
||||
)}
|
||||
{emojiSearch.trim() && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSelectIcon(emojiSearch.trim());
|
||||
setShowEmojiPicker(false);
|
||||
setEmojiSearch('');
|
||||
}}
|
||||
className="w-full text-left px-2 py-1 bg-accent/10 hover:bg-accent/20 text-accent rounded text-xs font-medium flex items-center justify-between cursor-pointer"
|
||||
>
|
||||
<span>Eingabe als Icon nutzen</span>
|
||||
<span className="text-base">{emojiSearch.trim()}</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="max-h-48 overflow-y-auto grid grid-cols-6 gap-1 pr-1">
|
||||
{filteredEmojis.map((item) => (
|
||||
<button
|
||||
key={item.emoji}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSelectIcon(item.emoji);
|
||||
setShowEmojiPicker(false);
|
||||
setEmojiSearch('');
|
||||
}}
|
||||
className="p-1.5 rounded-btn hover:bg-panel-elevated text-lg flex items-center justify-center cursor-pointer transition-colors"
|
||||
title={item.keywords}
|
||||
>
|
||||
{item.emoji}
|
||||
</button>
|
||||
))}
|
||||
{filteredEmojis.length === 0 && (
|
||||
<div className="col-span-6 text-center text-xs text-faint py-3">
|
||||
Kein passendes Standard-Icon
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
import React, { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { X, Trash2, Loader2, Clock } from 'lucide-react';
|
||||
import type { EmojiClickData } from 'emoji-picker-react';
|
||||
import { EmojiStyle, Theme, SuggestionMode } from 'emoji-picker-react';
|
||||
|
||||
// Lazy-load emoji-picker-react so the main bundle size stays small
|
||||
const EmojiPicker = React.lazy(() => import('emoji-picker-react'));
|
||||
|
||||
const RECENT_EMOJIS_STORAGE_KEY = 'mindsift_recent_emojis';
|
||||
const MAX_RECENT_EMOJIS = 10;
|
||||
|
||||
function getStoredRecentEmojis(): string[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(RECENT_EMOJIS_STORAGE_KEY);
|
||||
if (!raw) return ['📝', '💡', '🚀', '⭐', '🔥', '🎯', '📚', '💻'];
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
||||
return parsed.slice(0, MAX_RECENT_EMOJIS);
|
||||
}
|
||||
} catch {
|
||||
// fallback
|
||||
}
|
||||
return ['📝', '💡', '🚀', '⭐', '🔥', '🎯', '📚', '💻'];
|
||||
}
|
||||
|
||||
function saveRecentEmoji(emoji: string): void {
|
||||
try {
|
||||
const current = getStoredRecentEmojis();
|
||||
const updated = [emoji, ...current.filter((e) => e !== emoji)].slice(0, MAX_RECENT_EMOJIS);
|
||||
localStorage.setItem(RECENT_EMOJIS_STORAGE_KEY, JSON.stringify(updated));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
interface EmojiPickerModalProps {
|
||||
currentIcon?: string;
|
||||
onSelectEmoji: (emoji: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||
currentIcon,
|
||||
onSelectEmoji,
|
||||
onClose,
|
||||
}) => {
|
||||
const [recentEmojis, setRecentEmojis] = useState<string[]>(getStoredRecentEmojis);
|
||||
const [isDark, setIsDark] = useState<boolean>(() =>
|
||||
typeof document !== 'undefined'
|
||||
? document.documentElement.classList.contains('dark')
|
||||
: true
|
||||
);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Sync theme changes
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver(() => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
});
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class', 'data-theme'],
|
||||
});
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
// Close on Escape or click outside (desktop popover)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(e.target as Node)
|
||||
) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const handlePick = (emoji: string) => {
|
||||
saveRecentEmoji(emoji);
|
||||
setRecentEmojis(getStoredRecentEmojis());
|
||||
onSelectEmoji(emoji);
|
||||
};
|
||||
|
||||
const handlePickerEmojiClick = (emojiData: EmojiClickData) => {
|
||||
handlePick(emojiData.emoji);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Backdrop (< 640px) */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/60 z-40 sm:hidden backdrop-blur-xs"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Main Container: Mobile Bottom-Sheet (<640px) & Desktop Popover (>=640px) */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="
|
||||
fixed inset-x-0 bottom-0 z-50 rounded-t-2xl max-h-[85vh] bg-panel border-t border-border shadow-2xl flex flex-col p-3 space-y-3
|
||||
sm:absolute sm:inset-auto sm:left-0 sm:top-full sm:mt-1 sm:z-40 sm:w-[360px] sm:max-h-[520px] sm:rounded-card sm:border sm:border-border sm:shadow-xl
|
||||
"
|
||||
style={
|
||||
{
|
||||
// Inject MindSift CSS variables into emoji-picker-react for seamless theming
|
||||
'--epr-bg-color': 'var(--bg-panel)',
|
||||
'--epr-category-label-bg-color': 'var(--bg-panel)',
|
||||
'--epr-picker-border-color': 'var(--border-color)',
|
||||
'--epr-text-color': 'var(--text-main)',
|
||||
'--epr-search-input-bg-color': 'var(--bg-app)',
|
||||
'--epr-search-input-bg-color-active': 'var(--bg-panel-elevated)',
|
||||
'--epr-hover-bg-color': 'var(--bg-panel-elevated)',
|
||||
'--epr-focus-bg-color': 'var(--bg-panel-elevated)',
|
||||
'--epr-highlight-color': 'var(--accent-primary)',
|
||||
'--epr-category-icon-active-color': 'var(--accent-primary)',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{/* Mobile handle indicator */}
|
||||
<div className="w-10 h-1 bg-border rounded-full mx-auto sm:hidden shrink-0" />
|
||||
|
||||
{/* Top Header: Title, Actions & Close */}
|
||||
<div className="flex items-center justify-between gap-2 shrink-0 border-b border-border pb-2">
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold text-main">
|
||||
<span>Icon auswählen</span>
|
||||
{currentIcon && (
|
||||
<span className="text-base leading-none ml-1">{currentIcon}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{currentIcon && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectEmoji('')}
|
||||
className="h-11 sm:h-7 px-3 sm:px-2 rounded text-xs font-medium text-status-err hover:bg-status-err/10 flex items-center gap-1 transition-colors cursor-pointer"
|
||||
title="Icon entfernen"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
<span className="hidden sm:inline">Entfernen</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="w-11 h-11 sm:w-7 sm:h-7 rounded flex items-center justify-center text-muted hover:text-main hover:bg-panel-elevated transition-colors cursor-pointer"
|
||||
title="Schließen"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<X className="w-4 h-4 sm:w-3.5 sm:h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Emojis Bar */}
|
||||
{recentEmojis.length > 0 && (
|
||||
<div className="shrink-0 space-y-1.5">
|
||||
<div className="flex items-center gap-1 text-[11px] text-muted">
|
||||
<Clock className="w-3 h-3 text-accent" />
|
||||
<span>Zuletzt verwendet:</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 no-scrollbar">
|
||||
{recentEmojis.map((emoji) => (
|
||||
<button
|
||||
key={emoji}
|
||||
type="button"
|
||||
onClick={() => handlePick(emoji)}
|
||||
className="min-w-[44px] min-h-[44px] sm:min-w-[32px] sm:min-h-[32px] rounded-btn bg-panel-elevated hover:bg-canvas border border-border flex items-center justify-center text-xl sm:text-base cursor-pointer transition-transform active:scale-95 shrink-0"
|
||||
>
|
||||
{emoji}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Full Emoji Catalog via emoji-picker-react */}
|
||||
<div className="flex-1 min-h-[300px] overflow-hidden rounded mindsift-emoji-picker">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="h-64 flex flex-col items-center justify-center gap-2 text-xs text-muted">
|
||||
<Loader2 className="w-5 h-5 animate-spin text-accent" />
|
||||
<span>Emoji-Katalog wird geladen…</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EmojiPicker
|
||||
onEmojiClick={handlePickerEmojiClick}
|
||||
autoFocusSearch={true}
|
||||
theme={isDark ? Theme.DARK : Theme.LIGHT}
|
||||
emojiStyle={EmojiStyle.NATIVE}
|
||||
suggestedEmojisMode={SuggestionMode.RECENT}
|
||||
searchPlaceholder="Emoji suchen…"
|
||||
searchPlaceHolder="Emoji suchen…"
|
||||
width="100%"
|
||||
height="350px"
|
||||
previewConfig={{
|
||||
showPreview: false,
|
||||
}}
|
||||
skinTonesDisabled={false}
|
||||
lazyLoadEmojis={true}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -444,3 +444,30 @@ body {
|
||||
color: #f97316;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
EMOJI PICKER (emoji-picker-react Integration & Mobile Touch Tuning)
|
||||
========================================================================== */
|
||||
|
||||
.mindsift-emoji-picker .EmojiPickerReact {
|
||||
border: none !important;
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
/* Prevent iOS auto-zoom on input focus */
|
||||
.mindsift-emoji-picker .EmojiPickerReact input.epr-search-input {
|
||||
font-size: 1rem !important;
|
||||
min-height: 44px !important;
|
||||
}
|
||||
|
||||
/* Min 44px touch targets on mobile */
|
||||
.mindsift-emoji-picker .EmojiPickerReact button.epr-emoji {
|
||||
min-width: 44px !important;
|
||||
min-height: 44px !important;
|
||||
}
|
||||
|
||||
.mindsift-emoji-picker .EmojiPickerReact .epr-category-nav button {
|
||||
min-height: 44px !important;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user