Browse Source

Add custom icons

master
Joe Attardi 4 years ago
parent
commit
b63de87d61
  1. 1
      .gitignore
  2. 15
      css/emoji-button.css
  3. 10
      index.d.ts
  4. 5
      site/src/components/Sidebar.js
  5. 6
      site/src/examples/customIcons.js
  6. 6
      site/src/examples/fixedPosition.js
  7. 75
      site/src/pages/docs/api.js
  8. 95
      site/src/pages/docs/icons.js
  9. 2
      site/src/pages/docs/position.js
  10. 13
      site/src/pages/index.js
  11. 3
      site/static/close.svg
  12. 4
      site/static/search.svg
  13. 10
      src/categoryButtons.ts
  14. 6
      src/icons.ts
  15. 54
      src/search.ts
  16. 10
      src/types.ts

1
.gitignore

@ -1,3 +1,4 @@
.DS_Store
node_modules
dist
test.js

15
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);
}

10
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;
}
}

5
site/src/components/Sidebar.js

@ -31,6 +31,11 @@ export default function Sidebar() {
Custom Emojis
</Link>
</li>
<li>
<Link activeClassName={styles.active} to="/docs/icons">
Custom Icons
</Link>
</li>
<li>
<Link activeClassName={styles.active} to="/docs/plugins">
Plugins

6
site/src/examples/customIcons.js

@ -0,0 +1,6 @@
const picker = new EmojiButton({
icons: {
search: '/search.svg',
clearSearch: '/close.svg'
}
});

6
site/src/examples/fixedPosition.js

@ -0,0 +1,6 @@
const picker = new EmojiButton({
position: {
top: '0',
right: '0'
}
});

75
site/src/pages/docs/api.js

@ -216,6 +216,20 @@ export default function ApiDocs() {
</td>
</tr>
<tr>
<th scope="row">
<code>icons</code>
</th>
<td>icon definition object</td>
<td>none</td>
<td>
The custom icons, if any, to use. The object provided must be an{' '}
<a href="#iconDefinitions">icon definition object</a>. Not all
properties have to be provided - only the icons you want to
override.
</td>
</tr>
<tr>
<th scope="row">
<code>initialCategory</code>
@ -552,6 +566,67 @@ export default function ApiDocs() {
</li>
</ul>
<a name="iconDefinitions" />
<h2>Icon Definition Objects</h2>
<p>
An icon definition object must contain at least one of the following
properties:
</p>
<ul>
<li>
<code>search</code>: The search icon, displayed when there is no
search text entered.
</li>
<li>
<code>clearSearch</code>: The button to clear the search, displayed
when there is search text entered.
</li>
<li>
<code>notFound</code>: The icon to display when a search yields no
results.
</li>
<li>
<code>categories</code>: A nested object containing one or more of
the following:
<ul>
<li>
<code>recents</code>: The Recent Emojis category.
</li>
<li>
<code>smileys</code>: The Smileys &amp; Emotion category.
</li>
<li>
<code>peopls</code>: The People &amp; Body category.
</li>
<li>
<code>animals</code>: The Animals &amp; Nature category.
</li>
<li>
<code>food</code>: The Food &amp; Drink category.
</li>
<li>
<code>activities</code>: The Activities category.
</li>
<li>
<code>travel</code>: The Travel &amp; Places category.
</li>
<li>
<code>objects</code>: The Objects category.
</li>
<li>
<code>symbols</code>: The Symbols category.
</li>
<li>
<code>flags</code>: The Flags category.
</li>
<li>
<code>custom</code>: The Custom category.
</li>
</ul>
</li>
</ul>
<a name="i18n" />
<h2>I18N Strings</h2>
<p>

95
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 (
<DocLayout>
<h1>Custom Icons</h1>
<p>
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:
</p>
<ul>
<li>The search icon</li>
<li>The clear search icon</li>
<li>The category icons</li>
<li>The "not found" icon</li>
</ul>
<p>
To use custom icons, pass the <code>icons</code> picker option and an
object with one or more of the following properties:
</p>
<ul>
<li>
<code>search</code>: Shown in the search bar when there is no search
text entered.
</li>
<li>
<code>clearSearch</code>: Shown in the search bar when there is search
text entered.
</li>
<li>
<code>notFound</code>: Shown along with a "not found" message when a
search yields no emojis.
</li>
<li>
<code>categories</code>: A nested object mapping category IDs to their
custom icons. The valid category IDs are:
<ul>
<li>
<code>recents</code>
</li>
<li>
<code>smileys</code>
</li>
<li>
<code>people</code>
</li>
<li>
<code>animals</code>
</li>
<li>
<code>food</code>
</li>
<li>
<code>activities</code>
</li>
<li>
<code>travel</code>
</li>
<li>
<code>objects</code>
</li>
<li>
<code>symbols</code>
</li>
<li>
<code>flags</code>
</li>
<li>
<code>custom</code>
</li>
</ul>
</li>
</ul>
<Example
options={{
icons: {
search: '/search.svg',
clearSearch: '/close.svg'
}
}}
/>
<SourceFile src={customIconsExample} />
</DocLayout>
);
}

2
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() {
</p>
<Example options={{ position: { top: '0', right: '0' } }} />
<SourceFile src={fixedPositionExample} />
</DocLayout>
);
}

13
site/src/pages/index.js

@ -32,7 +32,18 @@ export default function Home() {
<h2>Demo</h2>
<section className={styles.demo}>
<div>
<Example />
<Example
options={{
icons: {
search: '/search.svg',
clearSearch: '/close.svg',
notFound: '/close.svg',
categories: {
animals: '/search.svg'
}
}
}}
/>
</div>
<div className={styles.code}>
<SourceFile src={indexExample} />

3
site/static/close.svg

@ -0,0 +1,3 @@
<svg id="i-close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="currentcolor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M2 30 L30 2 M30 30 L2 2" />
</svg>

After

Width:  |  Height:  |  Size: 242 B

4
site/static/search.svg

@ -0,0 +1,4 @@
<svg id="i-search" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="currentcolor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<circle cx="14" cy="14" r="12" />
<path d="M23 23 L30 30" />
</svg>

After

Width:  |  Height:  |  Size: 272 B

10
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);

6
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;
}

54
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()
);
}
}

10
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;
}
Loading…
Cancel
Save