Skip to content

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'].

Vue
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

Imports

ts
import { HLIconPicker } from '@platform-ui/highrise'

Props

NameTypeDefaultDescription
allowedTypesstring[]['emojis', 'gifs', 'icons']The types of icons to display.
defaultTabstring'emojis'The default tab to display.

Slots

NameDescription
emojisThe slot for the emojis.
gifsThe slot for the GIFs.
iconsThe slot for the icons.