лучшен выбор эмодзи для стикеров: добавлен модальный пикер эмодзи, оптимизирован список эмодзи, улучшен UX
This commit is contained in:
parent
4049792297
commit
ce75907087
34
src/components/shared/EmojiPickerModal.module.css
Normal file
34
src/components/shared/EmojiPickerModal.module.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.emojiContainer {
|
||||||
|
margin-bottom: var(--spacing-medium);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiItem {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: none;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiItem:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background-color: rgba(var(--color-primary-rgb), 0.1);
|
||||||
|
}
|
||||||
102
src/components/shared/EmojiPickerModal.tsx
Normal file
102
src/components/shared/EmojiPickerModal.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import styles from './NotificationModal.module.css';
|
||||||
|
import emojiStyles from './EmojiPickerModal.module.css';
|
||||||
|
|
||||||
|
interface EmojiPickerModalProps {
|
||||||
|
isVisible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSelect: (emoji: string) => void;
|
||||||
|
initialEmoji?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiList = [
|
||||||
|
// Смайлы и эмоции
|
||||||
|
'😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃', '😉', '😊',
|
||||||
|
'😇', '😍', '😘', '😗', '😚', '😙', '😋', '😛', '😜', '😝', '😐', '😑',
|
||||||
|
'😶', '😏', '😒', '🙄', '😬', '😌', '😔', '😪', '😴', '😷',
|
||||||
|
|
||||||
|
// Жесты и люди
|
||||||
|
'👍', '👎', '👌', '✌️', '👈', '👉', '👆', '👇', '👋', '✋', '👏', '🙌',
|
||||||
|
'👐', '🙏', '💪', '👂', '👃', '👀', '👄', '💋', '❤️', '💔', '💕', '💯',
|
||||||
|
|
||||||
|
// Животные
|
||||||
|
'🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁',
|
||||||
|
'🐮', '🐷', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦',
|
||||||
|
'🐤', '🐣', '🦆', '🦅', '🦉', '🐺', '🐗', '🐴', '🦄', '🐝',
|
||||||
|
'🐛', '🦋', '🐌', '🐞', '🐜', '🕷️', '🕸️',
|
||||||
|
|
||||||
|
// Еда и напитки
|
||||||
|
'🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒',
|
||||||
|
'🍑', '🍍', '🍅', '🍆', '🌽', '🍞', '🧀', '🍳', '🍗', '🍖', '🌭',
|
||||||
|
'🍔', '🍟', '🍕', '🍝', '🍜', '🍲', '🍛', '🍣', '🍱', '🍤', '🍙',
|
||||||
|
|
||||||
|
// Активности и объекты
|
||||||
|
'⚽', '🏀', '🏈', '⚾', '🎾', '🏐', '🏉', '🎱', '🏓', '🏸', '🏒', '🏑',
|
||||||
|
'⛳', '🏹', '🎣', '🏊', '🏄', '🏆', '🥇', '🥈', '🥉', '🏅',
|
||||||
|
|
||||||
|
// Символы
|
||||||
|
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕',
|
||||||
|
'💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮️', '✝️', '☪️', '🕉️', '☸️',
|
||||||
|
'✡️', '🔯', '🕎', '☯️', '☦️', '🛐', '⛎', '♈', '♉', '♊', '♋', '♌', '♍',
|
||||||
|
'♎', '♏', '♐', '♑', '♒', '♓', '❌', '⭕', '🛑'
|
||||||
|
];
|
||||||
|
|
||||||
|
const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
|
||||||
|
isVisible,
|
||||||
|
onCancel,
|
||||||
|
onSelect,
|
||||||
|
initialEmoji = '😊'
|
||||||
|
}) => {
|
||||||
|
const [selectedEmoji, setSelectedEmoji] = useState(initialEmoji);
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
const handleEmojiClick = (emoji: string) => {
|
||||||
|
setSelectedEmoji(emoji);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApply = () => {
|
||||||
|
onSelect(selectedEmoji);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.overlay}>
|
||||||
|
<div className={styles.modal}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.title}>Выберите эмодзи</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={emojiStyles.emojiContainer}>
|
||||||
|
<div className={emojiStyles.emojiGrid}>
|
||||||
|
{emojiList.map((emoji, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={`${emojiStyles.emojiItem} ${emoji === selectedEmoji ? emojiStyles.selected : ''}`}
|
||||||
|
onClick={() => handleEmojiClick(emoji)}
|
||||||
|
>
|
||||||
|
{emoji}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<button
|
||||||
|
className={`${styles.button} ${styles.secondaryButton}`}
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.button} ${styles.primaryButton}`}
|
||||||
|
onClick={handleApply}
|
||||||
|
>
|
||||||
|
Применить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmojiPickerModal;
|
||||||
@ -102,13 +102,15 @@
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emojiInput {
|
.emojiButton {
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { stickerService } from '../services/stickerService';
|
|||||||
import apiService from '../services/api';
|
import apiService from '../services/api';
|
||||||
import { GeneratedImage } from '../types/api';
|
import { GeneratedImage } from '../types/api';
|
||||||
import { getCurrentUserId } from '../constants/user';
|
import { getCurrentUserId } from '../constants/user';
|
||||||
|
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Транслитерирует кириллический текст в латиницу.
|
* Транслитерирует кириллический текст в латиницу.
|
||||||
@ -46,6 +47,7 @@ function transliterate(text: string): string {
|
|||||||
return result.substring(0, 30);
|
return result.substring(0, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const CreateStickerPack: React.FC = () => {
|
const CreateStickerPack: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
@ -56,6 +58,8 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const titleInputRef = useRef<HTMLInputElement>(null);
|
const titleInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false);
|
||||||
|
const [activeEmojiIndex, setActiveEmojiIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
// Функция для сворачивания клавиатуры
|
// Функция для сворачивания клавиатуры
|
||||||
const dismissKeyboard = () => {
|
const dismissKeyboard = () => {
|
||||||
@ -72,6 +76,28 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция для открытия пикера эмодзи
|
||||||
|
const openEmojiPicker = (index: number, e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setActiveEmojiIndex(index);
|
||||||
|
setIsEmojiPickerVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для закрытия пикера эмодзи без выбора
|
||||||
|
const handleCancelEmojiPicker = () => {
|
||||||
|
setIsEmojiPickerVisible(false);
|
||||||
|
setActiveEmojiIndex(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для выбора эмодзи
|
||||||
|
const handleSelectEmoji = (emoji: string) => {
|
||||||
|
if (activeEmojiIndex !== null) {
|
||||||
|
handleEmojiChange(activeEmojiIndex, emoji);
|
||||||
|
}
|
||||||
|
setIsEmojiPickerVisible(false);
|
||||||
|
setActiveEmojiIndex(null);
|
||||||
|
};
|
||||||
|
|
||||||
// Загрузка доступных изображений
|
// Загрузка доступных изображений
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchImages = async () => {
|
const fetchImages = async () => {
|
||||||
@ -230,14 +256,12 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
|
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<div className={styles.emojiSelector}>
|
<div className={styles.emojiSelector}>
|
||||||
<input
|
<button
|
||||||
type="text"
|
className={styles.emojiButton}
|
||||||
value={emojis[selectedIndex]}
|
onClick={(e) => openEmojiPicker(selectedIndex, e)}
|
||||||
onChange={(e) => handleEmojiChange(selectedIndex, e.target.value)}
|
>
|
||||||
className={styles.emojiInput}
|
{emojis[selectedIndex] || '😊'}
|
||||||
maxLength={2}
|
</button>
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -259,6 +283,14 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Модальное окно выбора эмодзи */}
|
||||||
|
<EmojiPickerModal
|
||||||
|
isVisible={isEmojiPickerVisible && activeEmojiIndex !== null}
|
||||||
|
onCancel={handleCancelEmojiPicker}
|
||||||
|
onSelect={handleSelectEmoji}
|
||||||
|
initialEmoji={activeEmojiIndex !== null ? (emojis[activeEmojiIndex] || '😊') : '😊'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user