Components
Loading preview...
Here is Autocomplete component
@prebuiltui
npx shadcn@latest add https://21st.dev/r/prebuiltui/autocompleteimport * as React from 'react';
import { Autocomplete } from '@base-ui-components/react/autocomplete';
export default function ExampleEmojiPicker() {
const [pickerOpen, setPickerOpen] = React.useState(false);
const [textValue, setTextValue] = React.useState('');
const [searchValue, setSearchValue] = React.useState('');
const textInputRef = React.useRef<HTMLInputElement | null>(null);
function handleInsertEmoji(value: string | null) {
if (!value || !textInputRef.current) {
return;
}
const emoji = value;
const start = textInputRef.current.selectionStart ?? textInputRef.current.value.length ?? 0;
const end = textInputRef.current.selectionEnd ?? textInputRef.current.value.length ?? 0;
setTextValue((prev) => prev.slice(0, start) + emoji + prev.slice(end));
setPickerOpen(false);
const input = textInputRef.current;
if (input) {
input.focus();
const caretPos = start + emoji.length;
input.setSelectionRange(caretPos, caretPos);
}
}
return (
<div className="mx-auto w-[16rem]">
<div className="flex items-center gap-2">
<input
ref={textInputRef}
type="text"
className="h-10 flex-1 font-normal rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
placeholder="iMessage"
value={textValue}
onChange={(event) => setTextValue(event.target.value)}
/>
<Autocomplete.Root
items={emojiGroups}
cols={COLUMNS}
open={pickerOpen}
onOpenChange={setPickerOpen}
onOpenChangeComplete={() => setSearchValue('')}
value={searchValue}
onValueChange={(value, details) => {
if (details.reason !== 'item-press') {
setSearchValue(value);
}
}}
>
<Autocomplete.Trigger
className="size-10 rounded-md border border-gray-200 bg-[canvas] text-[1.25rem] text-gray-900 outline-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100"
aria-label="Choose emoji"
>
ð
</Autocomplete.Trigger>
<Autocomplete.Portal>
<Autocomplete.Positioner className="outline-none" sideOffset={4} align="end">
<Autocomplete.Popup className="[--input-container-height:3rem] max-w-[var(--available-width)] max-h-[min(20.5rem,var(--available-height))] origin-[var(--transform-origin)] rounded-lg bg-[canvas] shadow-lg shadow-gray-200 text-gray-900 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300">
<div className="mx-1 flex h-[var(--input-container-height)] w-64 items-center justify-center bg-[canvas] text-center">
<Autocomplete.Input
placeholder="Search emojisâĶ"
className="h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
/>
</div>
<Autocomplete.Empty className="px-4 pb-4 pt-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0">
No emojis found
</Autocomplete.Empty>
<Autocomplete.List className="max-h-[min(calc(20.5rem-var(--input-container-height)),calc(var(--available-height)-var(--input-container-height)))] overflow-auto scroll-pt-10 scroll-pb-[0.35rem] overscroll-contain">
{(group: EmojiGroup) => (
<Autocomplete.Group key={group.value} items={group.items} className="block">
<Autocomplete.GroupLabel className="sticky top-0 z-[1] m-0 w-full border-b border-gray-100 bg-[canvas] px-4 pb-1 pt-2 text-[0.75rem] font-semibold uppercase tracking-wide text-gray-600">
{group.label}
</Autocomplete.GroupLabel>
<div className="p-1" role="presentation">
{chunkArray(group.items, COLUMNS).map((row, rowIdx) => (
<Autocomplete.Row key={rowIdx} className="grid grid-cols-5">
{row.map((rowItem) => (
<Autocomplete.Item
key={rowItem.emoji}
value={rowItem}
className="group min-w-[var(--anchor-width)] select-none flex h-10 flex-col items-center justify-center rounded-md bg-transparent px-0.5 py-2 text-gray-900 outline-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-md data-[highlighted]:before:bg-gray-200"
onClick={() => {
handleInsertEmoji(rowItem.emoji);
setPickerOpen(false);
}}
>
<span className="mb-1 text-[1.5rem] leading-none">
{rowItem.emoji}
</span>
</Autocomplete.Item>
))}
</Autocomplete.Row>
))}
</div>
</Autocomplete.Group>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
</div>
</div>
);
}
const COLUMNS = 5;
function chunkArray<T>(array: T[], size: number): T[][] {
const result: T[][] = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
interface EmojiItem {
emoji: string;
value: string;
name: string;
}
interface EmojiGroup {
value: string;
label: string;
items: EmojiItem[];
}
export const emojiCategories = [
{
label: 'Smileys & Emotion',
emojis: [
{ emoji: 'ð', name: 'grinning face' },
{ emoji: 'ð', name: 'grinning face with big eyes' },
{ emoji: 'ð', name: 'grinning face with smiling eyes' },
{ emoji: 'ð', name: 'beaming face with smiling eyes' },
{ emoji: 'ð', name: 'grinning squinting face' },
{ emoji: 'ð
', name: 'grinning face with sweat' },
{ emoji: 'ðĪĢ', name: 'rolling on the floor laughing' },
{ emoji: 'ð', name: 'face with tears of joy' },
{ emoji: 'ð', name: 'slightly smiling face' },
{ emoji: 'ð', name: 'upside-down face' },
{ emoji: 'ð', name: 'winking face' },
{ emoji: 'ð', name: 'smiling face with smiling eyes' },
{ emoji: 'ð', name: 'smiling face with halo' },
{ emoji: 'ðĨ°', name: 'smiling face with hearts' },
{ emoji: 'ð', name: 'smiling face with heart-eyes' },
{ emoji: 'ðĪĐ', name: 'star-struck' },
{ emoji: 'ð', name: 'face blowing a kiss' },
{ emoji: 'ð', name: 'kissing face' },
{ emoji: 'âšïļ', name: 'smiling face' },
{ emoji: 'ð', name: 'kissing face with closed eyes' },
{ emoji: 'ð', name: 'kissing face with smiling eyes' },
{ emoji: 'ðĨē', name: 'smiling face with tear' },
{ emoji: 'ð', name: 'face savoring food' },
{ emoji: 'ð', name: 'face with tongue' },
{ emoji: 'ð', name: 'winking face with tongue' },
{ emoji: 'ðĪŠ', name: 'zany face' },
{ emoji: 'ð', name: 'squinting face with tongue' },
{ emoji: 'ðĪ', name: 'money-mouth face' },
{ emoji: 'ðĪ', name: 'hugging face' },
{ emoji: 'ðĪ', name: 'face with hand over mouth' },
],
},
{
label: 'Animals & Nature',
emojis: [
{ emoji: 'ðķ', name: 'dog face' },
{ emoji: 'ðą', name: 'cat face' },
{ emoji: 'ð', name: 'mouse face' },
{ emoji: 'ðđ', name: 'hamster' },
{ emoji: 'ð°', name: 'rabbit face' },
{ emoji: 'ðĶ', name: 'fox' },
{ emoji: 'ðŧ', name: 'bear' },
{ emoji: 'ðž', name: 'panda' },
{ emoji: 'ðĻ', name: 'koala' },
{ emoji: 'ðŊ', name: 'tiger face' },
{ emoji: 'ðĶ', name: 'lion' },
{ emoji: 'ðŪ', name: 'cow face' },
{ emoji: 'ð·', name: 'pig face' },
{ emoji: 'ð―', name: 'pig nose' },
{ emoji: 'ðļ', name: 'frog' },
{ emoji: 'ðĩ', name: 'monkey face' },
{ emoji: 'ð', name: 'see-no-evil monkey' },
{ emoji: 'ð', name: 'hear-no-evil monkey' },
{ emoji: 'ð', name: 'speak-no-evil monkey' },
{ emoji: 'ð', name: 'monkey' },
{ emoji: 'ð', name: 'chicken' },
{ emoji: 'ð§', name: 'penguin' },
{ emoji: 'ðĶ', name: 'bird' },
{ emoji: 'ðĪ', name: 'baby chick' },
{ emoji: 'ðĢ', name: 'hatching chick' },
{ emoji: 'ðĨ', name: 'front-facing baby chick' },
{ emoji: 'ðĶ', name: 'duck' },
{ emoji: 'ðĶ
', name: 'eagle' },
{ emoji: 'ðĶ', name: 'owl' },
{ emoji: 'ðĶ', name: 'bat' },
],
},
{
label: 'Food & Drink',
emojis: [
{ emoji: 'ð', name: 'red apple' },
{ emoji: 'ð', name: 'green apple' },
{ emoji: 'ð', name: 'tangerine' },
{ emoji: 'ð', name: 'lemon' },
{ emoji: 'ð', name: 'banana' },
{ emoji: 'ð', name: 'watermelon' },
{ emoji: 'ð', name: 'grapes' },
{ emoji: 'ð', name: 'strawberry' },
{ emoji: 'ðŦ', name: 'blueberries' },
{ emoji: 'ð', name: 'melon' },
{ emoji: 'ð', name: 'cherries' },
{ emoji: 'ð', name: 'peach' },
{ emoji: 'ðĨ', name: 'mango' },
{ emoji: 'ð', name: 'pineapple' },
{ emoji: 'ðĨĨ', name: 'coconut' },
{ emoji: 'ðĨ', name: 'kiwi fruit' },
{ emoji: 'ð
', name: 'tomato' },
{ emoji: 'ð', name: 'eggplant' },
{ emoji: 'ðĨ', name: 'avocado' },
{ emoji: 'ðĨĶ', name: 'broccoli' },
{ emoji: 'ðĨŽ', name: 'leafy greens' },
{ emoji: 'ðĨ', name: 'cucumber' },
{ emoji: 'ðķïļ', name: 'hot pepper' },
{ emoji: 'ðŦ', name: 'bell pepper' },
{ emoji: 'ð―', name: 'ear of corn' },
{ emoji: 'ðĨ', name: 'carrot' },
{ emoji: 'ðŦ', name: 'olive' },
{ emoji: 'ð§', name: 'garlic' },
{ emoji: 'ð§
', name: 'onion' },
{ emoji: 'ðĨ', name: 'potato' },
],
},
];
const emojiGroups: EmojiGroup[] = emojiCategories.map((category) => ({
value: category.label,
label: category.label,
items: category.emojis.map((emoji) => ({
...emoji,
value: emoji.name.toLowerCase(),
})),
}));