diff --git a/.gitignore b/.gitignore index 5078938..a330171 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store node_modules dist test.js diff --git a/css/emoji-button.css b/css/emoji-button.css index 25dd2ae..06e8d1d 100644 --- a/css/emoji-button.css +++ b/css/emoji-button.css @@ -291,6 +291,11 @@ top: calc(50% - 0.5em); } +.emoji-picker__search-icon img { + width: 1em; + height: 1em; +} + .emoji-picker__search-not-found { color: var(--secondary-text-color); text-align: center; @@ -316,6 +321,11 @@ font-size: 3em; } +.emoji-picker__search-not-found-icon img { + width: 1em; + height: 1em; +} + .emoji-picker__search-not-found h2 { margin: 0.5em 0; font-size: 1em; @@ -400,6 +410,11 @@ button.emoji-picker__category-button { outline: none; } +button.emoji-picker__category-button img { + width: var(--category-button-size); + height: var(--category-button-size); +} + .emoji-picker.keyboard button.emoji-picker__category-button:focus { outline: 1px dotted var(--focus-indicator-color); } diff --git a/index.d.ts b/index.d.ts index b80e4dd..d01a148 100644 --- a/index.d.ts +++ b/index.d.ts @@ -37,6 +37,7 @@ declare namespace EmojiButton { initialCategory?: Category | 'recents'; custom?: CustomEmoji[]; plugins?: Plugin[]; + icons?: Icons; } export interface FixedPosition { @@ -126,4 +127,13 @@ declare namespace EmojiButton { }; notFound: string; } + + export interface Icons { + search?: string; + clearSearch?: string; + categories?: { + [key in I18NCategory]?: string; + }; + notFound?: string; + } } diff --git a/site/src/components/Sidebar.js b/site/src/components/Sidebar.js index 095a13b..25f75b9 100644 --- a/site/src/components/Sidebar.js +++ b/site/src/components/Sidebar.js @@ -31,6 +31,11 @@ export default function Sidebar() { Custom Emojis +
  • + + Custom Icons + +
  • Plugins diff --git a/site/src/examples/customIcons.js b/site/src/examples/customIcons.js new file mode 100644 index 0000000..20dddb0 --- /dev/null +++ b/site/src/examples/customIcons.js @@ -0,0 +1,6 @@ +const picker = new EmojiButton({ + icons: { + search: '/search.svg', + clearSearch: '/close.svg' + } +}); diff --git a/site/src/examples/fixedPosition.js b/site/src/examples/fixedPosition.js new file mode 100644 index 0000000..7114e4d --- /dev/null +++ b/site/src/examples/fixedPosition.js @@ -0,0 +1,6 @@ +const picker = new EmojiButton({ + position: { + top: '0', + right: '0' + } +}); diff --git a/site/src/pages/docs/api.js b/site/src/pages/docs/api.js index 8834ca4..27ccbcd 100644 --- a/site/src/pages/docs/api.js +++ b/site/src/pages/docs/api.js @@ -216,6 +216,20 @@ export default function ApiDocs() { + + + icons + + icon definition object + none + + The custom icons, if any, to use. The object provided must be an{' '} + icon definition object. Not all + properties have to be provided - only the icons you want to + override. + + + initialCategory @@ -552,6 +566,67 @@ export default function ApiDocs() {
  • + +

    Icon Definition Objects

    +

    + An icon definition object must contain at least one of the following + properties: +

    + + +

    I18N Strings

    diff --git a/site/src/pages/docs/icons.js b/site/src/pages/docs/icons.js new file mode 100644 index 0000000..7ea8460 --- /dev/null +++ b/site/src/pages/docs/icons.js @@ -0,0 +1,95 @@ +import React from 'react'; + +import DocLayout from '../../components/DocLayout'; +import Example from '../../components/Example'; +import SourceFile from '../../components/SourceFile'; + +import customIconsExample from '!!raw-loader!../../examples/customIcons.js'; + +export default function IconsExample() { + return ( + +

    Custom Icons

    +

    + If the icons built in to Emoji Button don't go well with your app's look + and feel, you can supply your own icons for: +

    + + + +

    + To use custom icons, pass the icons picker option and an + object with one or more of the following properties: +

    + + + + + + + ); +} diff --git a/site/src/pages/docs/position.js b/site/src/pages/docs/position.js index b9a9174..a6d1072 100644 --- a/site/src/pages/docs/position.js +++ b/site/src/pages/docs/position.js @@ -5,6 +5,7 @@ import Example from '../../components/Example'; import SourceFile from '../../components/SourceFile'; import positionExample from '!!raw-loader!../../examples/position.js'; +import fixedPositionExample from '!!raw-loader!../../examples/fixedPosition.js'; export default function PositionExample() { return ( @@ -36,6 +37,7 @@ export default function PositionExample() {

    + ); } diff --git a/site/src/pages/index.js b/site/src/pages/index.js index 843b0cc..76e7cb8 100644 --- a/site/src/pages/index.js +++ b/site/src/pages/index.js @@ -32,7 +32,18 @@ export default function Home() {

    Demo

    - +
    diff --git a/site/static/close.svg b/site/static/close.svg new file mode 100644 index 0000000..fd6bd69 --- /dev/null +++ b/site/static/close.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/site/static/search.svg b/site/static/search.svg new file mode 100644 index 0000000..1a14171 --- /dev/null +++ b/site/static/search.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/categoryButtons.ts b/src/categoryButtons.ts index c3eb45b..d5d8dfc 100644 --- a/src/categoryButtons.ts +++ b/src/categoryButtons.ts @@ -49,7 +49,15 @@ export class CategoryButtons { categories.forEach((category: string) => { const button = createElement('button', CLASS_CATEGORY_BUTTON); - button.innerHTML = categoryIcons[category]; + + if (this.options.icons?.categories?.[category]) { + button.appendChild( + icons.createIcon(this.options.icons.categories[category]) + ); + } else { + button.innerHTML = categoryIcons[category]; + } + button.tabIndex = -1; button.title = this.i18n.categories[category]; container.appendChild(button); diff --git a/src/icons.ts b/src/icons.ts index 181c77d..96c7a4a 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -49,3 +49,9 @@ export const search = icon({ prefix: 'fas', iconName: 'search' }).html[0]; export const smile = icon({ prefix: 'far', iconName: 'smile' }).html[0]; export const times = icon({ prefix: 'fas', iconName: 'times' }).html[0]; export const user = icon({ prefix: 'fas', iconName: 'user' }).html[0]; + +export function createIcon(src: string): HTMLImageElement { + const img = document.createElement('img') as HTMLImageElement; + img.src = src; + return img; +} diff --git a/src/search.ts b/src/search.ts index a1f1d2e..2a43103 100644 --- a/src/search.ts +++ b/src/search.ts @@ -9,7 +9,7 @@ import { SHOW_SEARCH_RESULTS, HIDE_SEARCH_RESULTS } from './events'; -import { createElement } from './util'; +import { createElement, empty } from './util'; import { I18NStrings, EmojiButtonOptions, EmojiRecord } from './types'; import { @@ -22,13 +22,19 @@ import { } from './classes'; class NotFoundMessage { - constructor(private message: string) {} + constructor(private message: string, private iconUrl?: string) {} render(): HTMLElement { const container = createElement('div', CLASS_NOT_FOUND); const iconContainer = createElement('div', CLASS_NOT_FOUND_ICON); - iconContainer.innerHTML = icons.frown; + + if (this.iconUrl) { + iconContainer.appendChild(icons.createIcon(this.iconUrl)); + } else { + iconContainer.innerHTML = icons.frown; + } + container.appendChild(iconContainer); const messageContainer = createElement('h2'); @@ -90,7 +96,13 @@ export class Search { this.searchContainer.appendChild(this.searchField); this.searchIcon = createElement('span', CLASS_SEARCH_ICON); - this.searchIcon.innerHTML = icons.search; + + if (this.options.icons?.search) { + this.searchIcon.appendChild(icons.createIcon(this.options.icons.search)); + } else { + this.searchIcon.innerHTML = icons.search; + } + this.searchIcon.addEventListener('click', (event: MouseEvent) => this.onClearSearch(event) ); @@ -112,7 +124,15 @@ export class Search { this.searchField.value = ''; this.resultsContainer = null; - this.searchIcon.innerHTML = icons.search; + if (this.options.icons?.search) { + empty(this.searchIcon); + this.searchIcon.appendChild( + icons.createIcon(this.options.icons.search) + ); + } else { + this.searchIcon.innerHTML = icons.search; + } + this.searchIcon.style.cursor = 'default'; this.events.emit(HIDE_SEARCH_RESULTS); @@ -169,11 +189,26 @@ export class Search { if (event.key === 'Tab' || event.key === 'Shift') { return; } else if (!this.searchField.value) { - this.searchIcon.innerHTML = icons.search; + if (this.options.icons?.search) { + empty(this.searchIcon); + this.searchIcon.appendChild( + icons.createIcon(this.options.icons.search) + ); + } else { + this.searchIcon.innerHTML = icons.search; + } + this.searchIcon.style.cursor = 'default'; this.events.emit(HIDE_SEARCH_RESULTS); } else { - this.searchIcon.innerHTML = icons.times; + if (this.options.icons?.clearSearch) { + empty(this.searchIcon); + this.searchIcon.appendChild( + icons.createIcon(this.options.icons.clearSearch) + ); + } else { + this.searchIcon.innerHTML = icons.times; + } this.searchIcon.style.cursor = 'pointer'; const searchResults = this.emojiData.filter( emoji => @@ -208,7 +243,10 @@ export class Search { } else { this.events.emit( SHOW_SEARCH_RESULTS, - new NotFoundMessage(this.i18n.notFound).render() + new NotFoundMessage( + this.i18n.notFound, + this.options.icons?.notFound + ).render() ); } } diff --git a/src/types.ts b/src/types.ts index 9618f81..8bb825f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -58,6 +58,7 @@ export interface EmojiButtonOptions { initialCategory?: Category | 'recents'; custom?: EmojiRecord[]; plugins?: Plugin[]; + icons?: Icons; } export interface FixedPosition { @@ -112,3 +113,12 @@ export interface I18NStrings { }; notFound: string; } + +export interface Icons { + search?: string; + clearSearch?: string; + categories?: { + [key in I18NCategory]?: string; + }; + notFound?: string; +} \ No newline at end of file