Icon, Emoji, and GIF Picker
A component for selecting icons, emojis, and GIFs with a modern interface. The component is fully customizable with slots for each type.
Basic Usage
html
<HLIconPicker :allowed-types="['emojis', 'gifs', 'icons']">
<template #emojis>
<div class="hr-emoji-container">
<Picker
:data="emojiIndex"
:emoji-size="21"
native
:per-line="9"
color="var(--primary-700)"
:show-preview="false"
:emoji-tooltip="false"
:i18n="i18n"
@select="showEmoji"
>
<template #searchTemplate="{ onSearch }">
<div class="emoji-search-container">
<HLInput
id="search"
v-model:model-value="emojiSearch"
:prefix-icon="SearchMdIcon"
size="md"
placeholder="Search for an emoji"
@update:model-value="onSearch"
/>
</div>
</template>
</Picker>
</div>
</template>
<template #gifs>
<div :key="top_10_gifs.length" class="hr-gif-container">
<div class="gif-search-container">
<HLInput
id="search"
v-model:model-value="gifSearch"
:prefix-icon="SearchMdIcon"
size="md"
placeholder="Search for GIFs"
@update:model-value="searchGif"
/>
</div>
<div class="gif-list">
<div v-for="(gif, index) in top_10_gifs" :key="index" class="gif-item" @click="selectGif(gif)">
<img :src="gif.media_formats.nanogif.url" alt="gif" />
</div>
</div>
</div>
</template>
<template #icons>
<div class="hr-icon-container">
<div class="hr-icon-search-container">
<HLInput
id="icon-search"
v-model:model-value="iconSearch"
:prefix-icon="SearchMdIcon"
size="md"
placeholder="Search for an icon"
@update:model-value="searchIcon"
/>
</div>
<div class="icons">
<div v-for="icon in icons" :key="icon" class="icon-item">
<Icon :icon="icon" @click="selectIcon(icon)" />
</div>
</div>
</div>
</template>
</HLIconPicker>ts
import { iconToHTML, iconToSVG, replaceIDs } from '@iconify/utils'
import { Icon, getIcon } from '@iconify/vue'
import { HLIconPicker, HLInput } from '@platform-ui/highrise'
import 'emoji-mart-vue-fast/css/emoji-mart.css'
import data from 'emoji-mart-vue-fast/data/all.json'
import { EmojiIndex, Picker } from 'emoji-mart-vue-fast/src'
import { debounce } from 'lodash-es'
import { onMounted, ref } from 'vue'
import { SearchMdIcon } from '@gohighlevel/ghl-icons/24/outline'
const emit = defineEmits(['select-gif', 'select-emoji', 'select-icon'])
// start of emoji
// https://github.com/serebrov/emoji-mart-vue
let emojiIndex = new EmojiIndex(data)
function showEmoji(emoji: any) {
// console.log(emoji)
emit('select-emoji', emoji)
console.log(emoji)
}
const i18n = {
search: 'Search for an emoji',
notfound: 'No emoji found',
categories: {
search: 'Search Results',
recent: 'Recent',
smileys: 'Smileys',
people: 'People & Body',
nature: 'Animals & Nature',
foods: 'Food & Drink',
activity: 'Activity',
places: 'Travel & Places',
objects: 'Objects',
symbols: 'My Symbols',
flags: 'My Flags',
custom: 'My Custom',
},
}
// end of emoji
// start of gif
// url Async requesting function
async function httpGetAsync(theUrl): Promise<any> {
const response = await fetch(theUrl)
return response.json()
}
const top_10_gifs = ref([])
// function to call the trending and category endpoints
async function grab_data() {
// set the apikey and limit
var apikey = '**********'
var clientkey = 'my_test_app'
var lmt = 10
var featured_url = 'https://tenor.googleapis.com/v2/featured?key=' + apikey + '&client_key=' + clientkey + '&limit=' + lmt
const response = await httpGetAsync(featured_url)
top_10_gifs.value = response.results
// data will be loaded by each call's callback
return
}
// SUPPORT FUNCTIONS ABOVE
// MAIN BELOW
// start the flow
grab_data()
const emojiSearch = ref('')
const gifSearch = ref('')
async function searchGif() {
// test search term
if (!gifSearch.value) {
grab_data()
return
}
var search_term = gifSearch.value
var apikey = '**********'
var clientkey = 'my_test_app'
var lmt = 10
// using default locale of en_US
var search_url =
'https://tenor.googleapis.com/v2/search?q=' + search_term + '&key=' + apikey + '&client_key=' + clientkey + '&limit=' + lmt
const response = await httpGetAsync(search_url)
top_10_gifs.value = response.results
}
const selectGif = gif => {
emit('select-gif', gif)
console.log(gif)
}
// end of gif
// start of icon
const getIconsFromAPI = (apiEndpoint: string, searchStr?: string) => {
return new Promise((resolve, reject) => {
fetch(`${apiEndpoint}/search?query=${searchStr}&limit=100`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to retrieve icons. Please try again later.')
}
return response.json()
})
.then(response => {
return resolve(response.icons)
})
.catch(err => {
reject(err)
})
})
}
const iconifyUrl = 'https://api.iconify.design'
const icons = ref<any[]>([])
const iconSearch = ref('social')
const dfb = debounce(() => {
const searchTerm = iconSearch.value || 'social'
getIconsFromAPI(iconifyUrl, searchTerm).then(response => {
icons.value = response as any[]
})
}, 500)
const searchIcon = () => {
dfb()
}
onMounted(() => {
getIconsFromAPI(iconifyUrl, 'social').then(response => {
icons.value = response as any[]
})
console.log(icons.value)
})
// end of icon
const selectIcon = (icon: any) => {
const iconData: any = getIcon(icon)
const svgData = iconToSVG(iconData, {
height: '100%',
width: '100%',
})
const constructedSVG = iconToHTML(replaceIDs(svgData.body), svgData.attributes)
emit('select-icon', icon, constructedSVG)
console.log(icon, constructedSVG)
}Only Emojis and Icons
Don't pass the gifs slot to the component, and allowed-types should be ['emojis', 'icons'].
html
<HLIconPicker :allowed-types="['emojis', 'icons']">
<template #emojis>
<div class="hr-emoji-container">
<Picker
:data="emojiIndex"
:emoji-size="21"
native
:per-line="9"
color="var(--primary-700)"
:show-preview="false"
:emoji-tooltip="false"
:i18n="i18n"
@select="showEmoji"
>
<template #searchTemplate="{ onSearch }">
<div class="emoji-search-container">
<HLInput
id="search"
v-model:model-value="emojiSearch"
:prefix-icon="SearchMdIcon"
size="md"
placeholder="Search for an emoji"
@update:model-value="onSearch"
/>
</div>
</template>
</Picker>
</div>
</template>
<template #icons>
<div class="hr-icon-container">
<div class="hr-icon-search-container">
<HLInput
id="icon-search"
v-model:model-value="iconSearch"
:prefix-icon="SearchMdIcon"
size="md"
placeholder="Search for an icon"
@update:model-value="searchIcon"
/>
</div>
<div class="icons">
<div v-for="icon in icons" :key="icon" class="icon-item">
<Icon :icon="icon" @click="selectIcon(icon)" />
</div>
</div>
</div>
</template>
</HLIconPicker>Libraries
- Emojis: emoji-mart-vue-fast
- GIFs: tenor-js
- Icons: @iconify/vue
Imports
ts
import { HLIconPicker } from '@platform-ui/highrise'Props
| Name | Type | Default | Description |
|---|---|---|---|
allowedTypes | string[] | ['emojis', 'gifs', 'icons'] | The types of icons to display. |
defaultTab | string | 'emojis' | The default tab to display. |
Slots
| Name | Description |
|---|---|
emojis | The slot for the emojis. |
gifs | The slot for the GIFs. |
icons | The slot for the icons. |