Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype selectable emoji. #268

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,61 @@ Note: CSS also includes background images for image-based emoji sets (apple, goo
| **pickerStyles** | | | Inline styles applied to the root element. Useful for positioning |
| **title** | | `Emoji Mart™` | The title shown when no emojis are hovered |
| **infiniteScroll** | | `true` | Scroll continuously through the categories |
| **selectedEmoji** | | `undefined` | Display "Selected" category with emoji passed as 'selectedEmoji' |

| Event | Description |
| --------------- | ----------------------- |
| **select** | Params: `(emoji) => {}` |
| **skin-change** | Params: `(skin) => {}` |

#### Selectable picker

Selectable picker added in #253 and allows using `Picker` component as a selector to select and unselect a single emoji.

Here is an example:

```javascript
<template>
<div>
<emoji v-if="selectedEmoji" :data="index" :emoji="selectedEmoji" :size="32" />
<picker
:data="index"
:selectedEmoji="selectedEmoji"
@select="selectableSelectEmoji"
@unselect="selectableUnselectEmoji"
/>
</div>
</template>

<script>
import data from 'emoji-mart-vue-fast/data/all.json'
import 'emoji-mart-vue-fast/css/emoji-mart.css'
import { Picker, Emoji, EmojiIndex } from 'emoji-mart-vue-fast'

export default {
data() {
return {
index: new EmojiIndex(data),
emoji: 'point_up',
selectedEmoji: undefined,
}
},
methods: {
selectableSelectEmoji(emoji) {
this.selectedEmoji = emoji
},
selectableUnselectEmoji(emoji) {
this.selectedEmoji = undefined
},
},
components: {
Picker,
Emoji,
},
}
</script>
```

#### Picker Usage And Native Emoji vs Images

The `select` event described above can be handled to insert the emoji into the text area or use it in any other way.
Expand Down
5 changes: 5 additions & 0 deletions css/emoji-mart.css
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,8 @@
display: none;
visibility: hidden;
}
.emoji-delete {
vertical-align: top;
margin-left: -21px;
margin-top: -3px;
}
34 changes: 34 additions & 0 deletions docs/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@
></iframe>
</div>

<div class="row"></div>
<h2>Selectable Example</h2>
<div class="row">
<button @click="toggleSelectable">Show / hide the picker</button>
<emoji
v-if="selectedEmoji"
:data="index"
:emoji="selectedEmoji"
:size="32"
/>
<emoji v-else :data="index" emoji=":dotted_line_face:" :size="32" />
</div>
<div class="row">
<picker
v-if="selectableVisible"
:data="index"
:selectable="true"
:selectedEmoji="selectedEmoji"
@select="selectableSelectEmoji"
@unselect="selectableUnselectEmoji"
/>
</div>

<div class="row"></div>
<h2>QDialog Example</h2>
<div class="row">
Expand Down Expand Up @@ -270,6 +293,8 @@ export default {
emojisOutput: '',
selectedEmojis: [],
flagsVisible: true,
selectedEmoji: undefined,
selectableVisible: false,
}
},
computed: {
Expand Down Expand Up @@ -306,6 +331,15 @@ export default {
this.emojisOutput = this.emojisOutput + '?'
}
},
selectableSelectEmoji(emoji) {
this.selectedEmoji = emoji
},
selectableUnselectEmoji(emoji) {
this.selectedEmoji = undefined
},
toggleSelectable() {
this.selectableVisible = !this.selectableVisible
},
},
components: {
Picker,
Expand Down
2 changes: 2 additions & 0 deletions spec/__snapshots__/picker-spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ exports[`Picker renders correctly 1`] = `
id="emoji-mart-list"
role="listbox"
>
<!---->

<section
aria-label="Frequently Used"
class="emoji-mart-category"
Expand Down
92 changes: 90 additions & 2 deletions spec/picker-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Picker,
Category,
Preview,
SelectedEmoji,
Emoji,
} from '../src/components'

Expand Down Expand Up @@ -117,6 +118,8 @@ describe('categories', () => {
})

describe('categories exclude preview emoji', () => {
const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation()

let index = new EmojiIndex(data, {
exclude: ['places'],
})
Expand All @@ -126,7 +129,16 @@ describe('categories exclude preview emoji', () => {
},
})

// Note: the error is printed into console.
// The error is printed into console.
expect(consoleErrorMock.mock.calls.length).toEqual(2)
const error = consoleErrorMock.mock.calls[0][0]
expect(error).toEqual(
'Default preview emoji `department_store` is not available, check the Picker `emoji` property',
)
const error2 = consoleErrorMock.mock.calls[1][0]
expect(error2.message).toContain('Can not find emoji by id: department_store')
consoleErrorMock.mockRestore()

it('will not throw an error if default emoji is not available', () => {
expect(picker.vm.emoji).toEqual('department_store')
// When `emoji` (that is emoji id used for idleEmoji) is not available,
Expand All @@ -138,6 +150,8 @@ describe('categories exclude preview emoji', () => {
})

describe('categories include allows to select and order categories', () => {
const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation()

let index = new EmojiIndex(data, {
// Note: the 'recent' category is always first.
include: ['nature', 'smileys', 'recent'],
Expand All @@ -148,7 +162,15 @@ describe('categories include allows to select and order categories', () => {
},
})

// Note: the error is printed into console.
// The error is printed into console.
const error = consoleErrorMock.mock.calls[0][0]
expect(error).toEqual(
'Default preview emoji `department_store` is not available, check the Picker `emoji` property',
)
const error2 = consoleErrorMock.mock.calls[1][0]
expect(error2.message).toContain('Can not find emoji by id: department_store')
consoleErrorMock.mockRestore()

it('will not throw an error if default emoji is not available', () => {
let categories = picker.findAllComponents(Category)
expect(categories.length).toBe(3)
Expand Down Expand Up @@ -377,3 +399,69 @@ describe('emjoiSize', () => {
)
})
})

describe('selectable emjoi', () => {
let index = new EmojiIndex(data)
// The selected emoji is passed ouside the component.
const picker = mount(Picker, {
propsData: {
data: index,
selectedEmoji: index.emoji('grinning'),
},
})

it('selectable emoji is displayed', async () => {
let selectedEmoji = picker.findComponent(SelectedEmoji)

expect(selectedEmoji.exists()).toBe(true)
expect(selectedEmoji.vm.emoji.id).toBe('grinning')

let label = picker.find('.emoji-mart-category-label')
expect(label.text()).toEqual('Selected')
})

it('selectable emoji removed when clicked', async () => {
let selectedEmoji = picker.findComponent(SelectedEmoji)
await selectedEmoji.find('.emoji-selected').trigger('click')

// Selected emoji emitted the 'click' event
let events = selectedEmoji.emitted().click
expect(events.length).toBe(1)
let emojiData = events[0][0]
expect(emojiData.id).toBe('grinning')

// Vefiy picker 'unselect' event.
events = picker.emitted().unselect
expect(events.length).toBe(1)
emojiData = events[0][0]
expect(emojiData.id).toBe('grinning')
})

it('selectable emoji removed when x icon clicked', async () => {
let selectedEmoji = picker.findComponent(SelectedEmoji)
await selectedEmoji.find('.emoji-delete').trigger('click')

// Selected emoji emitted the 'remove' event
let events = selectedEmoji.emitted().remove
expect(events.length).toBe(1)
let emojiData = events[0][0]
expect(emojiData).toBe(index.emoji('grinning'))
})
})

describe('selectable emjoi absent by default', () => {
let index = new EmojiIndex(data)
const picker = mount(Picker, {
propsData: {
data: index,
},
})

it('selectable emoji is not displayed', () => {
let emoji = picker.find('[data-title="grinning"]')
emoji.trigger('click')

let selectedEmoji = picker.findComponent(SelectedEmoji)
expect(selectedEmoji.exists()).toBe(false)
})
})
25 changes: 25 additions & 0 deletions src/components/Picker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@
role="listbox"
aria-expanded="true"
>
<SelectedEmoji
v-if="selectedEmoji"
:data="data"
:i18n="mergedI18n"
:emoji="selectedEmoji"
@click="onEmojiClick"
@remove="onUnselectEmoji"
/>

<category
v-for="(category, idx) in view.filteredCategories"
v-show="infiniteScroll || category == view.activeCategory"
Expand Down Expand Up @@ -83,6 +92,7 @@
:emoji-props="emojiProps"
:skin-props="skinProps"
:on-skin-change="onSkinChange"
@click="onUnselectEmoji"
/>
</div>
</slot>
Expand All @@ -100,10 +110,12 @@ import Anchors from './anchors.vue'
import Category from './category.vue'
import Preview from './preview.vue'
import Search from './search.vue'
import SelectedEmoji from './selectedEmoji.vue'

const I18N = {
search: 'Search',
notfound: 'No Emoji Found',
selected: 'Selected',
categories: {
search: 'Search Results',
recent: 'Frequently Used',
Expand Down Expand Up @@ -236,13 +248,25 @@ export default {
// for example, if we search for "asdf".
return
}
if (this.selectedEmoji == this.view.previewEmoji) {
// Selecting the same emoji will uneslect it.
this.$emit('unselect', this.selectedEmoji)
return
}
this.$emit('select', this.view.previewEmoji)
frequently.add(this.view.previewEmoji)
},
onEmojiClick(emoji) {
if (this.selectedEmoji == emoji) {
this.$emit('unselect', emoji)
return
}
this.$emit('select', emoji)
frequently.add(emoji)
},
onUnselectEmoji() {
this.$emit('unselect', this.selectedEmoji)
},
onTextSelect($event) {
// Prevent default text select event.
// In Vue 3 it goes through the component and triggers the `select`
Expand Down Expand Up @@ -271,6 +295,7 @@ export default {
Category,
Preview,
Search,
SelectedEmoji,
},
}
</script>
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { default as Search } from './search.vue'
export { default as Skins } from './skins.vue'
export { default as Emoji } from './Emoji.vue'
export { default as Picker } from './Picker.vue'
export { default as SelectedEmoji } from './selectedEmoji.vue'
9 changes: 9 additions & 0 deletions src/components/preview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
:native="emojiProps.native"
:skin="emojiProps.skin"
:set="emojiProps.set"
@click="onClick()"
/>
</div>

Expand All @@ -31,6 +32,7 @@
:native="emojiProps.native"
:skin="emojiProps.skin"
:set="emojiProps.set"
@click="onClick()"
/>
</div>

Expand Down Expand Up @@ -85,6 +87,7 @@ export default {
required: true
}
},
emits: ["click"],
computed: {
emojiData() {
if (this.emoji) {
Expand All @@ -100,6 +103,12 @@ export default {
return this.emojiData.emoticons
}
},
methods: {
onClick() {
console.log('preview click', this.emojiObject)
this.$emit('click', this.emojiObject)
},
},
components: {
Emoji,
Skins
Expand Down
Loading