85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
'use client';
|
||
|
||
|
||
|
||
import { useMemo, useState } from 'react';
|
||
|
||
import { Search } from 'lucide-react';
|
||
|
||
import { Input } from '@/components/ui/input';
|
||
|
||
import { EMOJI_CATEGORIES, EMOJI_DEFINITIONS, searchEmojis, type EmojiCategoryId } from '@/lib/emoji-catalog';
|
||
|
||
import { emojiIdToNative } from '@/lib/emoji-native-map';
|
||
|
||
import { cn } from '@/lib/utils';
|
||
|
||
|
||
|
||
interface EmojiPickerProps {
|
||
|
||
onSelect: (nativeEmoji: string) => void;
|
||
|
||
className?: string;
|
||
|
||
}
|
||
|
||
|
||
|
||
export function EmojiPicker({ onSelect, className }: EmojiPickerProps) {
|
||
|
||
const [category, setCategory] = useState<EmojiCategoryId>('smileys');
|
||
|
||
const [query, setQuery] = useState('');
|
||
|
||
|
||
|
||
const items = useMemo(() => {
|
||
|
||
const filtered = searchEmojis(query);
|
||
|
||
if (query.trim()) return filtered;
|
||
|
||
return filtered.filter((item) => item.category === category);
|
||
|
||
}, [category, query]);
|
||
|
||
|
||
|
||
return (
|
||
|
||
<div className={cn('rounded-2xl border border-[#dce3ec] bg-white shadow-lg', className)}>
|
||
|
||
<div className="border-b border-[#eceef4] p-2">
|
||
|
||
<div className="relative">
|
||
|
||
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#a8adbc]" />
|
||
|
||
<Input
|
||
|
||
value={query}
|
||
|
||
onChange={(event) => setQuery(event.target.value)}
|
||
|
||
placeholder="Поиск смайликов"
|
||
|
||
className="rounded-xl pl-9"
|
||
|
||
/>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{!query.trim() ? (
|
||
|
||
<div className="flex gap-1 overflow-x-auto border-b border-[#eceef4] px-2 py-2">
|
||
|
||
{EMOJI_CATEGORIES.map((item) => {
|
||
|
||
const native = emojiIdToNative(item.icon) ?? '✨';
|
||
|
||
return (
|
||
|
||
<button
|
||
|
||
key={item.id}
|
||
|
||
type="button"
|
||
|
||
title={item.label}
|
||
|
||
onClick={() => setCategory(item.id)}
|
||
|
||
className={cn(
|
||
|
||
'flex h-9 w-9 shrink-0 items-center justify-center rounded-xl text-xl transition',
|
||
|
||
category === item.id ? 'bg-[#3390ec]/10 ring-1 ring-[#3390ec]/30' : 'hover:bg-[#f4f5f8]'
|
||
|
||
)}
|
||
|
||
>
|
||
|
||
{native}
|
||
|
||
</button>
|
||
|
||
);
|
||
|
||
})}
|
||
|
||
</div>
|
||
|
||
) : null}
|
||
|
||
<div className="grid max-h-[280px] grid-cols-8 gap-1 overflow-y-auto p-2 sm:grid-cols-9">
|
||
|
||
{items.map((item) => {
|
||
|
||
const native = emojiIdToNative(item.id) ?? '✨';
|
||
|
||
return (
|
||
|
||
<button
|
||
|
||
key={item.id}
|
||
|
||
type="button"
|
||
|
||
title={item.label}
|
||
|
||
onClick={() => onSelect(native)}
|
||
|
||
className="flex h-10 w-10 items-center justify-center rounded-xl text-2xl transition hover:bg-[#f4f5f8]"
|
||
|
||
>
|
||
|
||
{native}
|
||
|
||
</button>
|
||
|
||
);
|
||
|
||
})}
|
||
|
||
{!items.length ? <p className="col-span-full px-2 py-6 text-center text-sm text-[#667085]">Ничего не найдено</p> : null}
|
||
|
||
</div>
|
||
|
||
{!query.trim() ? (
|
||
|
||
<p className="border-t border-[#eceef4] px-3 py-2 text-[11px] text-[#667085]">
|
||
|
||
{EMOJI_CATEGORIES.find((item) => item.id === category)?.label} · {EMOJI_DEFINITIONS.filter((item) => item.category === category).length}{' '}
|
||
|
||
смайликов
|
||
|
||
</p>
|
||
|
||
) : null}
|
||
|
||
</div>
|
||
|
||
);
|
||
|
||
}
|
||
|